31 Commits

Author SHA1 Message Date
Jonathan Miller c3e989d150 feat: sync updates.xml to main via PR (respects branch protection)
Creates chore/update-xml-<version> branch, updates file, creates PR,
auto-merges, cleans up branch. Replaces direct API file push.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 03:16:21 -05:00
Jonathan Miller d146b5d51e fix: derive element from XML filename, not display name
Plugins like MokoWaaS have display name "System - MokoWaaS" but
element should be "mokowaas" (from mokowaas.xml filename).
Falls back to repo name for generic filenames like templateDetails.xml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 02:45:31 -05:00
Jonathan Miller 4cf967f92b fix: stream-based tags (stable not vXX), derive element from repo name
- release_tag=stable instead of v${MAJOR}
- download URLs use /stable/ path
- Element fallback uses repo name not display name
- Updated channel-to-workflow docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 02:19:03 -05:00
Jonathan Miller 4d99ab9a4e fix: git push -u origin HEAD for version branch (no upstream)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:33:11 -05:00
Jonathan Miller 617344c4d7 fix: GH_MIRROR_TOKEN → GH_TOKEN in all templates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:12:12 -05:00
Jonathan Miller b57de90cef fix: add VERSION header to updates.xml in all workflow templates
Auto-release and update-server now write the copyright + VERSION
comment header when generating/rebuilding updates.xml.
Updated updates.xml.template scaffold to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:09:04 -05:00
Jonathan Miller dbd7ec8ae6 fix: hardcode MokoStandards-API branch to main (remove {{standards_branch}} placeholder)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:03:21 -05:00
Jonathan Miller f30c0dc9f9 docs: update multi-channel architecture — cascading channel updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 16:50:10 -05:00
Jonathan Miller dcd22dcfdc feat: cascading update channels — stable updates all, rc updates rc+below, etc
Channels cascade downward:
- stable → development, alpha, beta, rc, stable
- rc → development, alpha, beta, rc
- beta → development, alpha, beta
- alpha → development, alpha
- development → development

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 16:49:33 -05:00
Jonathan Miller adcbd2d2f4 chore: add .claude-worktree*/ to all gitignore templates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:20:56 -05:00
Jonathan Miller 14b4477ff2 docs: document auto-bump on all branches in multi-channel architecture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 12:30:21 -05:00
Jonathan Miller 032c32637f feat: auto-bump patch on all branches including dev
Previously dev branches were excluded from auto-bump. Now all
stability branches (dev, alpha, beta, rc) bump patch automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 12:26:57 -05:00
Jonathan Miller 16a86a94b7 docs: add multi-channel updates.xml architecture, update Joomla template listings
- Add Multi-Channel updates.xml Architecture section to README.md
- Document auto-release.yml.template and update-server.yml.template
- Update joomla/index.md with current template inventory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 12:17:46 -05:00
Jonathan Miller b68a23622a fix: remove patch 00 skip in auto-release template, all patches release
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 12:11:31 -05:00
Jonathan Miller 005ae12598 feat: MySQL export reads from config files, hardcode jmiller permissions
export-mysql.yml.template:
- Reads MySQL credentials from remote config files automatically:
  - Joomla: configuration.php ($user, $password, $db)
  - Dolibarr: conf/conf.php ($dolibarr_main_db_*)
- No MySQL secrets needed — credentials come from the app config
- Auto-detects platform (Joomla vs Dolibarr)
- Removed DEV_MYSQL_PASSWORD/DEMO_MYSQL_PASSWORD secret requirements

Permission hardcoding:
- Added ALLOWED_USERS="jmiller gitea-actions[bot]" to:
  deploy-demo, deploy-dev, deploy-rs, branch-freeze templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:18:48 -05:00
Jonathan Miller 3834781899 feat: add pull-from-dev and export-mysql workflow templates
pull-from-dev.yml.template:
- Downloads files from dev server via rsync/SSH into repo src/
- Configurable via DEV_SSH_HOST, DEV_SSH_USERNAME, DEV_PULL_PATH vars
- Auth via DEV_SSH_KEY secret
- Dry-run mode, branch selection, diff preview

export-mysql.yml.template:
- Exports MySQL database from dev or demo server
- Supports both Joomla and Dolibarr environments
- Sanitizes PII: passwords (bcrypt), emails, sessions, API keys, tokens
- Preserves admin/moko emails, strips everything else
- Dolibarr-specific: clears api_key, pass_crypted, ldap_pass, oauth secrets
- Saves as artifact (30d retention) or commits to sql/exports/
- Configurable per environment (dev/demo) via org or repo variables

Required variables (org or repo):
- DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME
- DEV_MYSQL_DATABASE, DEV_MYSQL_USER
- Secrets: DEV_SSH_KEY, DEV_MYSQL_PASSWORD

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 15:02:01 -05:00
Jonathan Miller c00a04087f Fix: protected files skip entirely before stale token check
Protected files (like updates.xml) were being overwritten because
the stale-token check ran AFTER the canOverwrite gate. Now protected
files continue (skip) immediately, even with --force.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 14:10:14 -05:00
Jonathan Miller 2b9bfb032e Protect updates.xml from bulk sync overwrite
Set protected=true, remove template reference. updates.xml is managed
by the release workflow, not bulk sync — sync was replacing it with
a stub template containing {{EXTENSION_NAME}} placeholders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 13:07:35 -05:00
Jonathan Miller b9109c51bc docs: update release cycle — Gitea-only pre-release, dual stable downloads
- Added platform distribution table (stable=dual, pre-release=Gitea only)
- Updated all example URLs from GitHub to Gitea
- Stable gets dual <downloadurl> (Gitea + GitHub)
- RC/Beta/Alpha/Dev get single <downloadurl> (Gitea only)
- Updated targetplatform to [56].*
- Updated Dolibarr update.txt URL to Gitea
- Removed sha256/client fields from examples (not used)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 13:04:32 -05:00
Jonathan Miller 0f9f110c2d Gitea-primary: update definitions, sync lib, token guidance
- waas-component.tf: 27 lines — GitHub URLs→Gitea, GA_TOKEN guidance,
  gitea-actions[bot], jmiller username
- joomla-template.tf: same pattern
- RepositorySynchronizer.php: jmiller-moko→jmiller

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 12:45:23 -05:00
Jonathan Miller 4cf931e7a3 fix: align updates.xml template with MokoCassiopeia format
- Removed copyright/FILE INFORMATION header (not needed in synced XML)
- Hardcoded org names: MokoConsulting (Gitea), mokoconsulting-tech (GitHub)
- Download URLs formatted with line breaks matching MokoCassiopeia
- Target platform: [56].* (matches Joomla 5.x and 6.x)
- PHP minimum: 8.1 (matching live repos)
- Removed {{GITEA_ORG}}/{{GITHUB_ORG}} tokens — orgs are fixed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 01:01:26 -05:00
Jonathan Miller a7f758f888 fix: remove self-require and fix script paths in composer.json
- Removed mokoconsulting-tech/enterprise self-reference from require
  (package cannot require itself)
- Fixed phpcs/phpstan script paths: api/ → lib/ validate/ automation/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 00:55:26 -05:00
Jonathan Miller 7b863f690d fix: remove all stale api/ path references across PHP codebase
Updated ~60 files: comments, usage docs, SCRIPT_PATH constants,
wrapper paths, require paths, error messages, and help text.
All api/validate/ → validate/, api/automation/ → automation/, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 19:18:22 -05:00
Jonathan Miller bd53fe834f feat: add gitignore validation, move bulk-repo-sync workflow here
- Add REQUIRED_GITIGNORE_ENTRIES constant with mandatory patterns:
  Sublime project/workspace, sftp-config, IDE dirs, secrets, vendor, logs
- Add validateGitignoreEntries() method for checking required entries
- mergeGitConfigFile() still appends missing entries (non-destructive)
- Add .gitea/workflows/bulk-repo-sync.yml (moved from MokoStandards)
  - Runs from this repo directly (checkout self, not remote)
  - Org updated to MokoConsulting (Gitea)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 18:17:24 -05:00
Jonathan Miller 784f423973 Fix remaining --jq, --paginate, --input flags in workflow templates
branch-freeze, repository-cleanup, manage-repo-templates converted
from gh CLI flags to curl/jq equivalents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 17:03:33 -05:00
Jonathan Miller 4742dfcbec fix: rename update.xml → updates.xml across all definitions and templates
Standardizes the Joomla update server filename to `updates.xml` (plural)
across all .tf definitions, workflow templates, and automation scripts.
The singular `update.xml` was inconsistent with the Joomla convention
and the updates.xml.template already in use.

Files fixed: 16 (definitions, templates, automation scripts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:39:21 -05:00
Jonathan Miller 5dff3346f0 Fix auto-release template: use Gitea API for main sync, auth push URL
- Replace git push to main with Gitea contents API (bypasses branch protection)
- Add authenticated push URL step after checkout
- Matches MokoCassiopeia release.yml pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 12:53:23 -05:00
Jonathan Miller 029033c2f6 Fix: set authenticated push URL in auto-release template for branch protection 2026-04-18 12:34:43 -05:00
Jonathan Miller 700e0abaac Fix: auto-release pushes updates.xml to main for update server
When releasing from a non-main branch, updates.xml is cherry-picked
to main so the Joomla update server always serves current data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 12:02:21 -05:00
Jonathan Miller bbadbfd2ad Fix: jmiller-moko→jmiller, --jq→pipe jq, github-actions→gitea-actions
Remaining cleanup across 12 workflow templates:
- repo_health, auto-assign, auto-dev-issue, branch-freeze,
  deploy-*, repository-cleanup, terraform templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:39:44 -05:00
Jonathan Miller c3fe454eb6 Fix: remove sha256: prefix from update XML templates (Joomla expects raw hex) 2026-04-18 11:33:30 -05:00
101 changed files with 1620 additions and 809 deletions
+112
View File
@@ -0,0 +1,112 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards-API.Automation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /.gitea/workflows/bulk-repo-sync.yml
# VERSION: 04.06.12
# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos
name: Bulk Repository Sync
on:
schedule:
- cron: '0 0 1 * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Preview mode (no changes)'
required: false
type: boolean
default: true
repos:
description: 'Comma-separated repo names (empty = all)'
required: false
type: string
default: ''
exclude:
description: 'Comma-separated repos to skip'
required: false
type: string
default: ''
force:
description: 'Force overwrite protected files'
required: false
type: boolean
default: false
permissions:
contents: write
issues: write
pull-requests: write
jobs:
bulk-sync:
name: Sync Standards to Repositories
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: json, mbstring, curl
tools: composer
coverage: none
- name: Install Dependencies
run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
- name: Build CLI Arguments
id: args
run: |
ARGS="--org MokoConsulting"
if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then
ARGS="$ARGS --dry-run"
fi
if [ -n "${{ inputs.repos }}" ]; then
ARGS="$ARGS --repos ${{ inputs.repos }}"
fi
if [ -n "${{ inputs.exclude }}" ]; then
ARGS="$ARGS --exclude ${{ inputs.exclude }}"
fi
if [ "${{ inputs.force }}" = "true" ]; then
ARGS="$ARGS --force"
fi
ARGS="$ARGS --yes"
echo "args=$ARGS" >> $GITHUB_OUTPUT
- name: Run Bulk Sync
run: |
echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}"
php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GIT_PLATFORM: gitea
GITEA_URL: https://git.mokoconsulting.tech
GITEA_ORG: MokoConsulting
- name: Commit Updated Definitions
if: success() && inputs.dry_run != 'true'
run: |
if [ -n "$(git status --porcelain definitions/sync/)" ]; then
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@git.mokoconsulting.tech"
git add definitions/sync/*.def.tf
git commit -m "chore: update synced repository definitions" || true
git push || true
fi
- name: Upload Sync Log
if: always()
uses: actions/upload-artifact@v4
with:
name: bulk-sync-log-${{ gitea.run_number }}
path: /tmp/bulk_sync.log
retention-days: 30
+10 -10
View File
@@ -15,11 +15,11 @@
* BRIEF: Bulk scaffold and sync Joomla template repositories * BRIEF: Bulk scaffold and sync Joomla template repositories
* *
* USAGE * USAGE
* php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme
* php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator
* php api/automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme * php automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme
* php api/automation/bulk_joomla_template.php --sync --all * php automation/bulk_joomla_template.php --sync --all
* php api/automation/bulk_joomla_template.php --list * php automation/bulk_joomla_template.php --list
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -717,13 +717,13 @@ class BulkJoomlaTemplate extends CLIApp
// ── Sync updates.xml between platforms ─────────────────────────────── // ── Sync updates.xml between platforms ───────────────────────────────
/** /**
* Sync updates.xml (or update.xml) between Gitea and GitHub for Joomla repos. * Sync updates.xml (or updates.xml) between Gitea and GitHub for Joomla repos.
* *
* Reads the file from both platforms, compares by latest <version> tag, * Reads the file from both platforms, compares by latest <version> tag,
* and pushes the newer one to the stale platform. * and pushes the newer one to the stale platform.
* *
* Designed to be called from a CI workflow via: * Designed to be called from a CI workflow via:
* php api/automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia * php automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia
*/ */
private function syncUpdatesBetweenPlatforms(string $org): int private function syncUpdatesBetweenPlatforms(string $org): int
{ {
@@ -788,7 +788,7 @@ class BulkJoomlaTemplate extends CLIApp
$name = $repo['name']; $name = $repo['name'];
$this->log("\n[{$name}]", 'INFO'); $this->log("\n[{$name}]", 'INFO');
// Try both update.xml and updates.xml filenames // Try both updates.xml and updates.xml filenames
$updateFile = $this->resolveUpdateFile($gitea, $github, $org, $name); $updateFile = $this->resolveUpdateFile($gitea, $github, $org, $name);
if ($updateFile === null) { if ($updateFile === null) {
$this->log(" ⊘ No update(s).xml found on either platform", 'INFO'); $this->log(" ⊘ No update(s).xml found on either platform", 'INFO');
@@ -849,7 +849,7 @@ class BulkJoomlaTemplate extends CLIApp
/** /**
* Find the updates file on both platforms, return the one with the higher version. * Find the updates file on both platforms, return the one with the higher version.
* *
* Checks both `updates.xml` and `update.xml` filenames. * Checks both `updates.xml` and `updates.xml` filenames.
* Returns the content from the platform with the newer <version>. * Returns the content from the platform with the newer <version>.
* Gitea wins ties (primary platform). * Gitea wins ties (primary platform).
* *
@@ -861,7 +861,7 @@ class BulkJoomlaTemplate extends CLIApp
string $org, string $org,
string $name string $name
): ?array { ): ?array {
$candidates = ['updates.xml', 'update.xml']; $candidates = ['updates.xml', 'updates.xml'];
$found = []; // platform => [name, content, version] $found = []; // platform => [name, content, version]
foreach (['gitea' => $gitea, 'github' => $github] as $platform => $adapter) { foreach (['gitea' => $gitea, 'github' => $github] as $platform => $adapter) {
+3 -3
View File
@@ -420,7 +420,7 @@ class BulkSync extends CLIApp
$this->log("The bulk repository sync is failing silently because the core", 'ERROR'); $this->log("The bulk repository sync is failing silently because the core", 'ERROR');
$this->log("synchronization logic has not been implemented yet.", 'ERROR'); $this->log("synchronization logic has not been implemented yet.", 'ERROR');
$this->log("", 'ERROR'); $this->log("", 'ERROR');
$this->log("Location: api/lib/Enterprise/RepositorySynchronizer.php", 'ERROR'); $this->log("Location: lib/Enterprise/RepositorySynchronizer.php", 'ERROR');
$this->log("Method: processRepository()", 'ERROR'); $this->log("Method: processRepository()", 'ERROR');
$this->log("", 'ERROR'); $this->log("", 'ERROR');
$this->log("Required Implementation:", 'ERROR'); $this->log("Required Implementation:", 'ERROR');
@@ -509,7 +509,7 @@ class BulkSync extends CLIApp
]); ]);
$script = basename(__FILE__); $script = basename(__FILE__);
$this->log("💾 Checkpoint saved. To resume once the issue is resolved, run:", 'INFO'); $this->log("💾 Checkpoint saved. To resume once the issue is resolved, run:", 'INFO');
$this->log(" php api/automation/{$script} --resume [same flags as before]", 'INFO'); $this->log(" php automation/{$script} --resume [same flags as before]", 'INFO');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->log("⚠️ Failed to save interrupt checkpoint: " . $e->getMessage(), 'WARN'); $this->log("⚠️ Failed to save interrupt checkpoint: " . $e->getMessage(), 'WARN');
} }
@@ -1355,7 +1355,7 @@ class BulkSync extends CLIApp
1. Check the local audit log or re-run with `--repos=<repo>` to see the specific error. 1. Check the local audit log or re-run with `--repos=<repo>` to see the specific error.
2. Fix the underlying issue (API token, rate limit, branch protection, etc.). 2. Fix the underlying issue (API token, rate limit, branch protection, etc.).
3. Re-run: `php api/automation/bulk_sync.php --org={$org} --repos=<repo> --force --yes` 3. Re-run: `php automation/bulk_sync.php --org={$org} --repos=<repo> --force --yes`
4. Close this issue once all repos are synced successfully. 4. Close this issue once all repos are synced successfully.
--- ---
+4 -4
View File
@@ -15,10 +15,10 @@
* BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance * BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance
* *
* USAGE * USAGE
* php api/automation/migrate_to_gitea.php --dry-run * php automation/migrate_to_gitea.php --dry-run
* php api/automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods * php automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods
* php api/automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived * php automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived
* php api/automation/migrate_to_gitea.php --resume * php automation/migrate_to_gitea.php --resume
*/ */
declare(strict_types=1); declare(strict_types=1);
+1 -1
View File
@@ -590,7 +590,7 @@ class PushFiles extends CLIApp
1. Check the output above for the specific error per repo. 1. Check the output above for the specific error per repo.
2. Fix the underlying issue (API token, branch permissions, file path, etc.). 2. Fix the underlying issue (API token, branch permissions, file path, etc.).
3. Re-run: `php api/automation/push_files.php --org={$org} --repos=<repo> --files=<files> --yes` 3. Re-run: `php automation/push_files.php --org={$org} --repos=<repo> --files=<files> --yes`
4. Close this issue once resolved. 4. Close this issue once resolved.
--- ---
+3 -3
View File
@@ -15,9 +15,9 @@
* BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def * BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def
* *
* USAGE * USAGE
* php api/cli/archive_repo.php --repo MokoOldModule * php cli/archive_repo.php --repo MokoOldModule
* php api/cli/archive_repo.php --repo MokoOldModule --dry-run * php cli/archive_repo.php --repo MokoOldModule --dry-run
* php api/cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open * php cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open
*/ */
declare(strict_types=1); declare(strict_types=1);
+5 -5
View File
@@ -15,10 +15,10 @@
* BRIEF: Create baseline GitHub Projects for repositories with standard fields and views * BRIEF: Create baseline GitHub Projects for repositories with standard fields and views
* *
* USAGE * USAGE
* php api/cli/create_project.php --repo MokoCRM # Auto-detect type, create project * php cli/create_project.php --repo MokoCRM # Auto-detect type, create project
* php api/cli/create_project.php --repo MokoCRM --type dolibarr # Force type * php cli/create_project.php --repo MokoCRM --type dolibarr # Force type
* php api/cli/create_project.php --org mokoconsulting-tech --all # All repos without projects * php cli/create_project.php --org mokoconsulting-tech --all # All repos without projects
* php api/cli/create_project.php --repo MokoCRM --dry-run # Preview without changes * php cli/create_project.php --repo MokoCRM --dry-run # Preview without changes
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -385,7 +385,7 @@ function createProject(
updateProjectV2(input: { updateProjectV2(input: {
projectId: $projectId, projectId: $projectId,
shortDescription: $shortDescription, shortDescription: $shortDescription,
readme: "Managed by MokoStandards. Run `php api/cli/create_project.php` to regenerate." readme: "Managed by MokoStandards. Run `php cli/create_project.php` to regenerate."
}) { }) {
projectV2 { id } projectV2 { id }
} }
+5 -5
View File
@@ -15,9 +15,9 @@
* BRIEF: Scaffold a new governed repository with full MokoStandards baseline * BRIEF: Scaffold a new governed repository with full MokoStandards baseline
* *
* USAGE * USAGE
* php api/cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module" * php cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module"
* php api/cli/create_repo.php --name MokoNewModule --type joomla --private * php cli/create_repo.php --name MokoNewModule --type joomla --private
* php api/cli/create_repo.php --name MokoNewModule --type generic --dry-run * php cli/create_repo.php --name MokoNewModule --type generic --dry-run
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -229,7 +229,7 @@ if (!$dryRun) {
if (file_exists($syncScript)) { if (file_exists($syncScript)) {
passthru("php " . escapeshellarg($syncScript) . " --repos " . escapeshellarg($name) . " --force --yes"); passthru("php " . escapeshellarg($syncScript) . " --repos " . escapeshellarg($name) . " --force --yes");
} else { } else {
echo " Run manually: php api/automation/bulk_sync.php --repos {$name} --force --yes\n"; echo " Run manually: php automation/bulk_sync.php --repos {$name} --force --yes\n";
} }
} else { } else {
echo " (dry-run) would run initial sync\n"; echo " (dry-run) would run initial sync\n";
@@ -242,7 +242,7 @@ if (!$dryRun) {
if (file_exists($projectScript)) { if (file_exists($projectScript)) {
passthru("php " . escapeshellarg($projectScript) . " --repo " . escapeshellarg($name) . " --type " . escapeshellarg($type)); passthru("php " . escapeshellarg($projectScript) . " --repo " . escapeshellarg($name) . " --type " . escapeshellarg($type));
} else { } else {
echo " Run manually: php api/cli/create_project.php --repo {$name} --type {$type}\n"; echo " Run manually: php cli/create_project.php --repo {$name} --type {$type}\n";
} }
} else { } else {
echo " (dry-run) would create Project\n"; echo " (dry-run) would create Project\n";
+4 -4
View File
@@ -15,10 +15,10 @@
* BRIEF: Joomla release pipeline — build ZIP+tar.gz, upload to GitHub Release, update updates.xml * BRIEF: Joomla release pipeline — build ZIP+tar.gz, upload to GitHub Release, update updates.xml
* *
* USAGE * USAGE
* php api/cli/joomla_release.php --repo MokoCassiopeia --stability stable * php cli/joomla_release.php --repo MokoCassiopeia --stability stable
* php api/cli/joomla_release.php --repo MokoCassiopeia --stability development * php cli/joomla_release.php --repo MokoCassiopeia --stability development
* php api/cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run * php cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run
* php api/cli/joomla_release.php --path /local/repo --stability stable * php cli/joomla_release.php --path /local/repo --stability stable
*/ */
declare(strict_types=1); declare(strict_types=1);
+5 -5
View File
@@ -13,10 +13,10 @@
* BRIEF: Automate the MokoStandards version branch release flow * BRIEF: Automate the MokoStandards version branch release flow
* *
* USAGE * USAGE
* php api/cli/release.php # Release current version * php cli/release.php # Release current version
* php api/cli/release.php --bump minor # Bump minor, then release * php cli/release.php --bump minor # Bump minor, then release
* php api/cli/release.php --bump major # Bump major, then release * php cli/release.php --bump major # Bump major, then release
* php api/cli/release.php --dry-run # Preview without changes * php cli/release.php --dry-run # Preview without changes
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -30,7 +30,7 @@ foreach ($argv as $i => $arg) {
} }
$repoRoot = dirname(__DIR__, 2); $repoRoot = dirname(__DIR__, 2);
$syncFile = "{$repoRoot}/api/lib/Enterprise/RepositorySynchronizer.php"; $syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php";
// Check both workflow directories for the bulk-repo-sync workflow // Check both workflow directories for the bulk-repo-sync workflow
$bulkSyncFile = file_exists("{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml") $bulkSyncFile = file_exists("{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml")
? "{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml" ? "{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml"
+4 -4
View File
@@ -15,10 +15,10 @@
* BRIEF: Apply branch protection rules to all repos via platform adapter * BRIEF: Apply branch protection rules to all repos via platform adapter
* *
* USAGE * USAGE
* php api/cli/sync_rulesets.php # Apply to all repos * php cli/sync_rulesets.php # Apply to all repos
* php api/cli/sync_rulesets.php --repo MokoCRM # Single repo * php cli/sync_rulesets.php --repo MokoCRM # Single repo
* php api/cli/sync_rulesets.php --dry-run # Preview only * php cli/sync_rulesets.php --dry-run # Preview only
* php api/cli/sync_rulesets.php --delete # Remove then re-apply * php cli/sync_rulesets.php --delete # Remove then re-apply
* *
* NOTE: On GitHub, this creates rulesets via the rulesets API. * NOTE: On GitHub, this creates rulesets via the rulesets API.
* On Gitea, this creates branch_protections via the branch protection API. * On Gitea, this creates branch_protections via the branch protection API.
+2 -3
View File
@@ -17,7 +17,6 @@
"ext-json": "*", "ext-json": "*",
"ext-zip": "*", "ext-zip": "*",
"guzzlehttp/guzzle": "^7.8", "guzzlehttp/guzzle": "^7.8",
"mokoconsulting-tech/enterprise": "dev-version/04",
"monolog/monolog": "^3.5", "monolog/monolog": "^3.5",
"php": ">=8.1", "php": ">=8.1",
"phpseclib/phpseclib": "^3.0", "phpseclib/phpseclib": "^3.0",
@@ -74,8 +73,8 @@
], ],
"scripts": { "scripts": {
"test": "phpunit", "test": "phpunit",
"phpcs": "phpcs --standard=phpcs.xml api/", "phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/",
"phpstan": "phpstan analyse -c phpstan.neon api/", "phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/",
"psalm": "psalm --config=psalm.xml", "psalm": "psalm --config=psalm.xml",
"check": [ "check": [
"@phpcs", "@phpcs",
+2 -2
View File
@@ -132,7 +132,7 @@ locals {
https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/raw/branch/main/updates.xml https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/raw/branch/main/updates.xml
</server> </server>
<server type="extension" priority="2" name="{{TEMPLATE_NAME}} Update Server"> <server type="extension" priority="2" name="{{TEMPLATE_NAME}} Update Server">
https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -179,7 +179,7 @@ locals {
https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip
</downloadurl> </downloadurl>
<downloadurl type="full" format="zip"> <downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip
</downloadurl> </downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="[56].*"/> <targetplatform name="joomla" version="[56].*"/>
+56 -56
View File
@@ -83,13 +83,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; managed by release workflow, never overwritten by sync"
required = true required = true
always_overwrite = false always_overwrite = false
protected = true
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -101,10 +101,10 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/raw/branch/main/update.xml https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/raw/branch/main/updates.xml
</server> </server>
<server type="extension" priority="2" name="{{EXTENSION_NAME}}"> <server type="extension" priority="2" name="{{EXTENSION_NAME}}">
https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/update.xml https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/raw/branch/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -123,7 +123,7 @@ locals {
https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip
</downloadurl> </downloadurl>
<downloadurl type="full" format="zip"> <downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip
</downloadurl> </downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="[56].*"/> <targetplatform name="joomla" version="[56].*"/>
@@ -377,7 +377,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -425,7 +425,7 @@ locals {
{ {
name = ".github" name = ".github"
path = ".github" path = ".github"
description = "GitHub-specific configuration" description = "Gitea/GitHub Actions configuration (Gitea reads .github/workflows natively)"
requirement_status = "suggested" requirement_status = "suggested"
purpose = "Contains GitHub Actions workflows and configuration" purpose = "Contains GitHub Actions workflows and configuration"
files = [ files = [
@@ -467,7 +467,7 @@ locals {
> | Placeholder | Where to find the value | > | Placeholder | Where to find the value |
> |---|---| > |---|---|
> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/<repo-name>` | > | `{{REPO_URL}}` | Full Gitea URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/<repo-name>` |
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root | > | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) | > | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
@@ -478,7 +478,7 @@ locals {
## What This Repo Is ## What This Repo Is
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
Repository URL: {{REPO_URL}} Repository URL: {{REPO_URL}}
Extension name: **{{EXTENSION_NAME}}** Extension name: **{{EXTENSION_NAME}}**
@@ -552,13 +552,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
The version="[56].*" regex matches Joomla 5.x and 6.x. --> The version="[56].*" regex matches Joomla 5.x and 6.x. -->
<updates> <updates>
<update> <update>
@@ -582,7 +582,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -611,22 +611,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="[56].*">` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x. - `<targetplatform name="joomla" version="[56].*">` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x.
@@ -635,8 +635,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -644,16 +644,16 @@ locals {
## GitHub Actions — Token Usage ## GitHub Actions — Token Usage
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases).
```yaml ```yaml
# ✅ Correct # ✅ Correct
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GA_TOKEN }}
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
``` ```
```yaml ```yaml
@@ -666,16 +666,16 @@ locals {
## MokoStandards Reference ## MokoStandards Reference
This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies:
| Document | Purpose | | Document | Purpose |
|----------|---------| |----------|---------|
| [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
| [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
| [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
| [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
| [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
--- ---
@@ -714,8 +714,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -728,8 +728,8 @@ locals {
- Never skip the FILE INFORMATION block on a new file - Never skip the FILE INFORMATION block on a new file
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Use `secrets.GA_TOKEN` for Gitea operations. Use `secrets.GH_TOKEN` only for GitHub mirror (stable/RC). Never use `secrets.GITHUB_TOKEN` directly
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -762,7 +762,7 @@ locals {
> | Placeholder | Where to find the value | > | Placeholder | Where to find the value |
> |---|---| > |---|---|
> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | > | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/<repo-name>` | > | `{{REPO_URL}}` | Full Gitea URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/<repo-name>` |
> | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description | > | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description |
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root | > | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
@@ -780,7 +780,7 @@ locals {
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
Repository URL: {{REPO_URL}} Repository URL: {{REPO_URL}}
This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories. This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
--- ---
@@ -789,7 +789,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -839,32 +839,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="[56].*">` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x. - `<targetplatform version="[56].*">` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x.
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -948,16 +948,16 @@ locals {
# GitHub Actions — Token Usage # GitHub Actions — Token Usage
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases).
```yaml ```yaml
# ✅ Correct # ✅ Correct
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GA_TOKEN }}
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
``` ```
```yaml ```yaml
@@ -973,8 +973,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -985,7 +985,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -999,7 +999,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -1010,12 +1010,12 @@ locals {
| Document | Purpose | | Document | Purpose |
|----------|---------| |----------|---------|
| [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
| [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
| [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
| [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
| [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
MOKO_END MOKO_END
} }
] ]
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" }, { path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -164,13 +164,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -182,7 +182,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -453,7 +453,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -628,13 +628,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -660,7 +660,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -689,22 +689,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -713,8 +713,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -792,8 +792,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -807,7 +807,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -867,7 +867,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -917,32 +917,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1051,8 +1051,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1063,7 +1063,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1077,7 +1077,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -164,13 +164,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -182,7 +182,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -453,7 +453,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -628,13 +628,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -660,7 +660,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -689,22 +689,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -713,8 +713,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -792,8 +792,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -807,7 +807,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -867,7 +867,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -917,32 +917,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1051,8 +1051,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1063,7 +1063,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1077,7 +1077,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" }, { path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" }, { path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "created" }, { path = "CODE_OF_CONDUCT.md" action = "created" },
{ path = "CONTRIBUTING.md" action = "created" }, { path = "CONTRIBUTING.md" action = "created" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" }, { path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "created" }, { path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+28 -28
View File
@@ -31,7 +31,7 @@ locals {
synced_files = [ synced_files = [
{ path = "LICENSE" action = "updated" }, { path = "LICENSE" action = "updated" },
{ path = "update.xml" action = "updated" }, { path = "updates.xml" action = "updated" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "updated" }, { path = "Makefile" action = "updated" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -149,13 +149,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -167,7 +167,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -605,13 +605,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -637,7 +637,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -666,22 +666,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -690,8 +690,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -769,8 +769,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -784,7 +784,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -844,7 +844,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -894,32 +894,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1028,8 +1028,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1040,7 +1040,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1054,7 +1054,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+29 -29
View File
@@ -34,7 +34,7 @@ locals {
{ path = "SECURITY.md" action = "updated" }, { path = "SECURITY.md" action = "updated" },
{ path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" },
{ path = "update.xml" action = "created" }, { path = "updates.xml" action = "created" },
{ path = "phpstan.neon" action = "updated" }, { path = "phpstan.neon" action = "updated" },
{ path = "Makefile" action = "created" }, { path = "Makefile" action = "created" },
{ path = ".gitignore" action = "updated" }, { path = ".gitignore" action = "updated" },
@@ -165,13 +165,13 @@ locals {
audience = "contributor" audience = "contributor"
}, },
{ {
name = "update.xml" name = "updates.xml"
extension = "xml" extension = "xml"
description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version"
required = true required = true
always_overwrite = false always_overwrite = false
audience = "developer" audience = "developer"
template = "templates/joomla/update.xml.template" template = "templates/joomla/updates.xml.template"
stub_content = <<-MOKO_END stub_content = <<-MOKO_END
<!-- <!--
Joomla Extension Update Server XML Joomla Extension Update Server XML
@@ -183,7 +183,7 @@ locals {
The manifest.xml in this repository must reference this file: The manifest.xml in this repository must reference this file:
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
@@ -454,7 +454,7 @@ locals {
{ {
name = "update-server.md" name = "update-server.md"
extension = "md" extension = "md"
description = "Joomla update server (update.xml) documentation" description = "Joomla update server (updates.xml) documentation"
required = true required = true
always_overwrite = true always_overwrite = true
template = "templates/docs/required/template-update-server-joomla.md" template = "templates/docs/required/template-update-server-joomla.md"
@@ -629,13 +629,13 @@ locals {
### Joomla Version Alignment ### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically. The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version> <version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release. <!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -661,7 +661,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below) ├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -690,22 +690,22 @@ locals {
--- ---
## update.xml — Required in Repo Root ## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. `updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`. - The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL). - The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -714,8 +714,8 @@ locals {
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -793,8 +793,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version | | New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version | | New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -808,7 +808,7 @@ locals {
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files - Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate - Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` - Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
MOKO_END MOKO_END
}, },
{ {
@@ -868,7 +868,7 @@ locals {
``` ```
{{REPO_NAME}}/ {{REPO_NAME}}/
├── manifest.xml # Joomla installer manifest (root — required) ├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required) ├── updates.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -918,32 +918,32 @@ locals {
|------|------------------------| |------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge | | `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag | | `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block | | `updates.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically. The `make release` command / release workflow syncs all three automatically.
--- ---
# update.xml — Required in Repo Root # updates.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml ```xml
<!-- In manifest.xml --> <!-- In manifest.xml -->
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <server type="extension" priority="1" name="{{EXTENSION_NAME}}">
{{REPO_URL}}/raw/main/update.xml {{REPO_URL}}/raw/main/updates.xml
</server> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved. - Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`. - `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL. - `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax). - `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release: Example `updates.xml` entry for a new release:
```xml ```xml
<updates> <updates>
<update> <update>
@@ -1052,8 +1052,8 @@ locals {
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | | New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | | New release | Prepend `<update>` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -1064,7 +1064,7 @@ locals {
- **Never commit directly to `main`** — all changes go through a PR. - **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. - **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.** - **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file. - **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. - **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`. - **Never mix tabs and spaces** within a file — follow `.editorconfig`.
@@ -1078,7 +1078,7 @@ locals {
Before opening a PR, verify: Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) - [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry - [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files - [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated - [ ] CHANGELOG.md updated
- [ ] Tests pass - [ ] Tests pass
+3 -3
View File
@@ -20,9 +20,9 @@
* changes — only XML manifest changes require a Joomla reinstall. * changes — only XML manifest changes require a Joomla reinstall.
* *
* USAGE * USAGE
* php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json * php deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json
* php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json --dry-run * php deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json --dry-run
* php api/deploy/deploy-joomla.php --path . --env dev * php deploy/deploy-joomla.php --path . --env dev
*/ */
declare(strict_types=1); declare(strict_types=1);
+6 -6
View File
@@ -96,25 +96,25 @@ CONFIG FORMAT
EXAMPLES EXAMPLES
# Dry-run preview of dev deployment # Dry-run preview of dev deployment
php api/deploy/deploy-sftp.php --env dev --dry-run --verbose php deploy/deploy-sftp.php --env dev --dry-run --verbose
# Deploy to dev server # Deploy to dev server
php api/deploy/deploy-sftp.php --path /repos/mymodule --env dev php deploy/deploy-sftp.php --path /repos/mymodule --env dev
# Deploy to release/production server # Deploy to release/production server
php api/deploy/deploy-sftp.php --path /repos/mymodule --env rs php deploy/deploy-sftp.php --path /repos/mymodule --env rs
# Use a different source directory # Use a different source directory
php api/deploy/deploy-sftp.php --env dev --src-dir htdocs php deploy/deploy-sftp.php --env dev --src-dir htdocs
# Explicit config with encrypted key # Explicit config with encrypted key
php api/deploy/deploy-sftp.php \ php deploy/deploy-sftp.php \
--path /repos/mymodule \ --path /repos/mymodule \
--env rs \ --env rs \
--key-passphrase "my passphrase" --key-passphrase "my passphrase"
# Quiet mode (errors only) # Quiet mode (errors only)
php api/deploy/deploy-sftp.php --env dev --quiet php deploy/deploy-sftp.php --env dev --quiet
EXIT CODES EXIT CODES
0 All files uploaded successfully 0 All files uploaded successfully
+26 -19
View File
@@ -95,93 +95,101 @@ development
The module descriptor's `url_last_version` should point to: The module descriptor's `url_last_version` should point to:
``` ```
https://raw.githubusercontent.com/{org}/{repo}/main/update.txt https://git.mokoconsulting.tech/MokoConsulting/{repo}/raw/branch/main/update.txt
``` ```
## Joomla: `updates.xml` (Multi-Entry) ## Joomla: `updates.xml` (Multi-Entry)
The `updates.xml` file contains **up to five stability entries at once** (one per stability level). Joomla reads the entire file and filters by the site's minimum stability setting. The `updates.xml` file contains **up to five stability entries at once** (one per stability level). Joomla reads the entire file and filters by the site's minimum stability setting.
### Platform Distribution
| Release Type | Gitea Release | GitHub Release | Download URLs |
|-------------|--------------|----------------|---------------|
| **Stable** | Yes | Yes (via mirror) | Dual (Gitea + GitHub) |
| **RC** | Yes | No | Single (Gitea only) |
| **Beta** | Yes | No | Single (Gitea only) |
| **Alpha** | Yes | No | Single (Gitea only) |
| **Development** | Yes | No | Single (Gitea only) |
Pre-release builds stay on Gitea for internal testing. Only stable releases are mirrored to GitHub.
### Complete Multi-Entry Example ### Complete Multi-Entry Example
```xml ```xml
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<updates> <updates>
<!-- Stable entry: visible to all sites (default minimum stability) --> <!-- Stable: dual download (Gitea + GitHub), visible to all sites -->
<update> <update>
<name>My Extension</name> <name>My Extension</name>
<description>My Extension stable release</description> <description>My Extension stable release</description>
<element>com_myextension</element> <element>com_myextension</element>
<type>component</type> <type>component</type>
<version>01.02.03</version> <version>01.02.03</version>
<client>site</client>
<tags> <tags>
<tag>stable</tag> <tag>stable</tag>
</tags> </tags>
<infourl title="My Extension">https://github.com/org/repo/releases/tag/v01</infourl> <infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://github.com/org/repo/releases/download/v01/com_myextension-01.02.03.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/v01/com_myextension-01.02.03.zip</downloadurl>
<downloadurl type="full" format="zip">https://github.com/mokoconsulting-tech/MyExtension/releases/download/v01/com_myextension-01.02.03.zip</downloadurl>
</downloads> </downloads>
<sha256>abc123...full-hash-here</sha256>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" /> <targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<!-- RC entry: visible to sites with minimum stability = RC or lower --> <!-- RC: Gitea only, visible to sites with minimum stability = RC or lower -->
<update> <update>
<name>My Extension</name> <name>My Extension</name>
<description>My Extension release candidate</description> <description>My Extension release candidate</description>
<element>com_myextension</element> <element>com_myextension</element>
<type>component</type> <type>component</type>
<version>01.03.01-rc</version> <version>01.03.01-rc</version>
<client>site</client>
<tags> <tags>
<tag>rc</tag> <tag>rc</tag>
</tags> </tags>
<infourl title="My Extension">https://github.com/org/repo/tree/rc/01.03.01</infourl> <infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/rc</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://github.com/org/repo/archive/refs/heads/rc/01.03.01.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/rc/com_myextension-01.03.01-rc.zip</downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" /> <targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<!-- Beta entry: visible to sites with minimum stability = Beta or lower --> <!-- Beta: Gitea only, visible to sites with minimum stability = Beta or lower -->
<update> <update>
<name>My Extension</name> <name>My Extension</name>
<description>My Extension beta build</description> <description>My Extension beta build</description>
<element>com_myextension</element> <element>com_myextension</element>
<type>component</type> <type>component</type>
<version>01.03.01-beta</version> <version>01.03.01-beta</version>
<client>site</client>
<tags> <tags>
<tag>beta</tag> <tag>beta</tag>
</tags> </tags>
<infourl title="My Extension">https://github.com/org/repo/tree/beta/01.03.01</infourl> <infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/beta</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://github.com/org/repo/archive/refs/heads/beta/01.03.01.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/beta/com_myextension-01.03.01-beta.zip</downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" /> <targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<!-- Alpha entry: visible to sites with minimum stability = Alpha or lower --> <!-- Alpha: Gitea only, visible to sites with minimum stability = Alpha or lower -->
<update> <update>
<name>My Extension</name> <name>My Extension</name>
<description>My Extension alpha build</description> <description>My Extension alpha build</description>
<element>com_myextension</element> <element>com_myextension</element>
<type>component</type> <type>component</type>
<version>01.03.01-alpha</version> <version>01.03.01-alpha</version>
<client>site</client>
<tags> <tags>
<tag>alpha</tag> <tag>alpha</tag>
</tags> </tags>
<infourl title="My Extension">https://github.com/org/repo/tree/alpha/01.03.01</infourl> <infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/alpha</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://github.com/org/repo/archive/refs/heads/alpha/01.03.01.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/alpha/com_myextension-01.03.01-alpha.zip</downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" /> <targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
@@ -195,7 +203,6 @@ The `updates.xml` file contains **up to five stability entries at once** (one pe
<element>com_myextension</element> <element>com_myextension</element>
<type>component</type> <type>component</type>
<version>01.04.00-dev</version> <version>01.04.00-dev</version>
<client>site</client>
<tags> <tags>
<tag>development</tag> <tag>development</tag>
</tags> </tags>
+1 -1
View File
@@ -11,7 +11,7 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /lib/CliBase.php * PATH: /lib/CliBase.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: Standalone base CLI class for api/ scripts that do not use CliFramework * BRIEF: Standalone base CLI class for scripts that do not use CliFramework
*/ */
declare(strict_types=1); declare(strict_types=1);
+1 -1
View File
@@ -11,7 +11,7 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /lib/Common.php * PATH: /lib/Common.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: Common utility functions for api/ scripts * BRIEF: Common utility functions for scripts
* NOTE: Version format used throughout is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04). * NOTE: Version format used throughout is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04).
* All version regex patterns enforce exactly two digits per component by design. * All version regex patterns enforce exactly two digits per component by design.
*/ */
@@ -92,7 +92,7 @@ class EnterpriseReadinessValidator
]; ];
foreach ($required as $library) { foreach ($required as $library) {
$phpFile = "{$path}/api/lib/Enterprise/{$library}.php"; $phpFile = "{$path}/lib/Enterprise/{$library}.php";
$this->addResult( $this->addResult(
"Enterprise library: {$library}", "Enterprise library: {$library}",
file_exists($phpFile), file_exists($phpFile),
+79 -3
View File
@@ -166,7 +166,7 @@ class RepositorySynchronizer
{ {
$this->logger->logInfo("Starting synchronization for {$org}/{$repo}"); $this->logger->logInfo("Starting synchronization for {$org}/{$repo}");
// Resolve repo root (two levels up from this file: Enterprise/ → lib/ → api/ → root) // Resolve repo root (three levels up from this file: Enterprise/ → lib/ → root)
// API repo root (definitions, sync code) // API repo root (definitions, sync code)
$repoRoot = dirname(dirname(__DIR__)); $repoRoot = dirname(dirname(__DIR__));
// MokoStandards repo root (templates, configs) // MokoStandards repo root (templates, configs)
@@ -509,7 +509,7 @@ HCL;
try { try {
$issueData = $this->adapter->createIssue($org, $repo, $issueTitle, $issueBody, [ $issueData = $this->adapter->createIssue($org, $repo, $issueTitle, $issueBody, [
'labels' => ['mokostandards', 'type: chore', 'automation'], 'labels' => ['mokostandards', 'type: chore', 'automation'],
'assignees' => ['jmiller-moko'], 'assignees' => ['jmiller'],
]); ]);
$issueNumber = $issueData['number'] ?? null; $issueNumber = $issueData['number'] ?? null;
$this->logger->logInfo("Created tracking issue #{$issueNumber}" . count($summary['copied']) . " files synced directly to {$defaultBranch}"); $this->logger->logInfo("Created tracking issue #{$issueNumber}" . count($summary['copied']) . " files synced directly to {$defaultBranch}");
@@ -598,7 +598,12 @@ HCL;
$isReadme = $basename === 'readme.md'; $isReadme = $basename === 'readme.md';
$isChangelog = in_array($basename, ['changelog.md', 'changelog'], true); $isChangelog = in_array($basename, ['changelog.md', 'changelog'], true);
$isProtected = $isReadme || $isChangelog; $isProtected = $isReadme || $isChangelog;
$canOverwrite = !$isProtected && ($force || $entry['always_overwrite']) && !($entry['protected'] ?? false); // Protected files are NEVER overwritten, even with --force
if ($entry['protected'] ?? false) {
$summary['skipped'][] = ['file' => $targetPath, 'reason' => 'Protected — never overwritten'];
continue;
}
$canOverwrite = !$isProtected && ($force || $entry['always_overwrite']);
if ($isReadme) { if ($isReadme) {
$summary['skipped'][] = ['file' => $targetPath, 'reason' => 'README — never overwritten']; $summary['skipped'][] = ['file' => $targetPath, 'reason' => 'README — never overwritten'];
@@ -903,6 +908,77 @@ HCL;
return $entries; return $entries;
} }
/**
* Required .gitignore entries that MUST exist in every governed repo.
* The sync validates these exist (appending if missing) without
* overwriting custom entries. Repos can add their own patterns freely.
*/
private const REQUIRED_GITIGNORE_ENTRIES = [
// Secrets & environment
'.env',
'.env.local',
'.env.*.local',
'secrets/',
'*.secrets.*',
// Sublime Text project files
'*.sublime-project',
'*.sublime-workspace',
'*.sublime-settings',
// SFTP config (Sublime SFTP, VS Code SFTP, etc.)
'sftp-config*.json',
'sftp-config.json.template',
'sftp-settings.json',
// IDE / editor
'.idea/',
'.vscode/*',
'.claude/',
'*.code-workspace',
// OS cruft
'.DS_Store',
'Thumbs.db',
// Task tracking
'TODO.md',
// Vendor / dependencies
'/vendor/',
'node_modules/',
// Logs
'*.log',
];
/**
* Validate that required .gitignore entries exist in a repo.
* Returns array of missing entries, empty if all present.
*
* @param string $existingContent Current .gitignore content from repo
* @return array<string> Missing required entries
*/
public function validateGitignoreEntries(string $existingContent): array
{
$existingLines = array_map('trim', explode("\n", $existingContent));
$existingSet = [];
foreach ($existingLines as $line) {
if ($line !== '' && !str_starts_with($line, '#')) {
$existingSet[$line] = true;
}
}
$missing = [];
foreach (self::REQUIRED_GITIGNORE_ENTRIES as $entry) {
if (!isset($existingSet[$entry])) {
$missing[] = $entry;
}
}
return $missing;
}
private function mergeGitConfigFile(string $existing, string $template): string private function mergeGitConfigFile(string $existing, string $template): string
{ {
$existingLines = array_map('rtrim', explode("\n", $existing)); $existingLines = array_map('rtrim', explode("\n", $existing));
+4 -4
View File
@@ -34,7 +34,7 @@ use MokoEnterprise\PlatformAdapterFactory;
* pinned commit SHA. Already-pinned references (40-char hex SHA) are left untouched. * pinned commit SHA. Already-pinned references (40-char hex SHA) are left untouched.
* *
* Usage: * Usage:
* php api/maintenance/pin_action_shas.php [--dry-run] [--verbose] [--help] * php maintenance/pin_action_shas.php [--dry-run] [--verbose] [--help]
* *
* Environment: * Environment:
* GH_TOKEN Personal access token for GitHub API calls. * GH_TOKEN Personal access token for GitHub API calls.
@@ -90,7 +90,7 @@ class ActionShaPinner
private function showHelp(): void private function showHelp(): void
{ {
echo <<<'HELP' echo <<<'HELP'
Usage: php api/maintenance/pin_action_shas.php [OPTIONS] Usage: php maintenance/pin_action_shas.php [OPTIONS]
Pins GitHub Actions to immutable commit SHAs in all .github/workflows/*.yml Pins GitHub Actions to immutable commit SHAs in all .github/workflows/*.yml
files. Already-pinned references (40-character commit SHA) are skipped. files. Already-pinned references (40-character commit SHA) are skipped.
@@ -106,10 +106,10 @@ Environment:
Examples: Examples:
# Preview all changes # Preview all changes
GH_TOKEN=ghp_xxx php api/maintenance/pin_action_shas.php --dry-run --verbose GH_TOKEN=ghp_xxx php maintenance/pin_action_shas.php --dry-run --verbose
# Apply changes # Apply changes
GH_TOKEN=ghp_xxx php api/maintenance/pin_action_shas.php GH_TOKEN=ghp_xxx php maintenance/pin_action_shas.php
HELP; HELP;
} }
+3 -3
View File
@@ -15,9 +15,9 @@
* BRIEF: Generate a live inventory dashboard of all governed repos as a GitHub issue * BRIEF: Generate a live inventory dashboard of all governed repos as a GitHub issue
* *
* USAGE * USAGE
* php api/maintenance/repo_inventory.php # Generate and post dashboard * php maintenance/repo_inventory.php # Generate and post dashboard
* php api/maintenance/repo_inventory.php --dry-run # Preview only * php maintenance/repo_inventory.php --dry-run # Preview only
* php api/maintenance/repo_inventory.php --json # JSON output to stdout * php maintenance/repo_inventory.php --json # JSON output to stdout
*/ */
declare(strict_types=1); declare(strict_types=1);
+4 -4
View File
@@ -15,10 +15,10 @@
* BRIEF: Audit FTP secrets and variables across all governed repos — report missing or stale * BRIEF: Audit FTP secrets and variables across all governed repos — report missing or stale
* *
* USAGE * USAGE
* php api/maintenance/rotate_secrets.php --all # Audit all repos * php maintenance/rotate_secrets.php --all # Audit all repos
* php api/maintenance/rotate_secrets.php --repo MokoCRM # Single repo * php maintenance/rotate_secrets.php --repo MokoCRM # Single repo
* php api/maintenance/rotate_secrets.php --all --json # JSON output * php maintenance/rotate_secrets.php --all --json # JSON output
* php api/maintenance/rotate_secrets.php --all --create-issue # Post results as issue * php maintenance/rotate_secrets.php --all --create-issue # Post results as issue
*/ */
declare(strict_types=1); declare(strict_types=1);
+1 -1
View File
@@ -25,7 +25,7 @@ declare(strict_types=1);
*/ */
class ScriptRegistryUpdater class ScriptRegistryUpdater
{ {
private const REGISTRY_PATH = 'api/.script-registry.json'; private const REGISTRY_PATH = '.script-registry.json';
private bool $dryRun = false; private bool $dryRun = false;
private bool $verbose = false; private bool $verbose = false;
+3 -3
View File
@@ -435,15 +435,15 @@ class UpdateVersionFromReadme extends CliFramework
"", "",
"1. Run the sync script locally:", "1. Run the sync script locally:",
" ```bash", " ```bash",
" php api/maintenance/update_version_from_readme.php --path . --dry-run", " php maintenance/update_version_from_readme.php --path . --dry-run",
" php api/maintenance/update_version_from_readme.php --path .", " php maintenance/update_version_from_readme.php --path .",
" ```", " ```",
"2. Inspect any files still flagged — they may use a non-standard VERSION format.", "2. Inspect any files still flagged — they may use a non-standard VERSION format.",
"3. Update them manually to match `VERSION: {$version}`.", "3. Update them manually to match `VERSION: {$version}`.",
"4. Commit and push — this issue will be closed automatically on the next successful sync.", "4. Commit and push — this issue will be closed automatically on the next successful sync.",
"", "",
"---", "---",
"*Automatically created by [update_version_from_readme.php](api/maintenance/update_version_from_readme.php)*", "*Automatically created by [update_version_from_readme.php](maintenance/update_version_from_readme.php)*",
]); ]);
try { try {
+1
View File
@@ -46,6 +46,7 @@ Icon?
.idea/ .idea/
.settings/ .settings/
.claude/ .claude/
.claude-worktree*/
.vscode/* .vscode/*
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/settings.json.example !.vscode/settings.json.example
+1
View File
@@ -46,6 +46,7 @@ Icon?
.idea/ .idea/
.settings/ .settings/
.claude/ .claude/
.claude-worktree*/
.vscode/* .vscode/*
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/settings.json.example !.vscode/settings.json.example
+1
View File
@@ -46,6 +46,7 @@ Icon?
.idea/ .idea/
.settings/ .settings/
.claude/ .claude/
.claude-worktree*/
.vscode/* .vscode/*
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/settings.json.example !.vscode/settings.json.example
+13 -47
View File
@@ -1,45 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version='1.0' encoding='UTF-8'?>
<!-- <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> SPDX-License-Identifier: GPL-3.0-or-later
VERSION: {{VERSION}}
-->
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
FILE INFORMATION
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards.Templates
REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards
PATH: /templates/joomla/updates.xml.template
VERSION: XX.YY.ZZ
BRIEF: Joomla update server XML template — lists available extension releases for Joomla auto-updates
NOTE: Dual-platform: Gitea (primary) and GitHub (backup mirror).
The manifest.xml <updateservers> must declare both servers.
Tokens replaced at sync time: {{REPO_NAME}}, {{GITEA_ORG}}, {{GITHUB_ORG}},
{{EXTENSION_NAME}}, {{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}, {{VERSION}},
{{MAINTAINER_URL}}
-->
<!--
Joomla Extension Update Server XML
See: https://docs.joomla.org/Deploying_an_Update_Server
This file is the update server manifest for {{EXTENSION_NAME}}.
The Joomla installer polls this URL to check for new versions.
The manifest.xml in this repository must reference BOTH update servers:
<updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}} Update Server (Gitea)">
https://git.mokoconsulting.tech/{{GITEA_ORG}}/{{REPO_NAME}}/raw/branch/main/updates.xml
</server>
<server type="extension" priority="2" name="{{EXTENSION_NAME}} Update Server (GitHub)">
https://raw.githubusercontent.com/{{GITHUB_ORG}}/{{REPO_NAME}}/main/updates.xml
</server>
</updateservers>
When a new release is made, run `make release` or the release workflow to
prepend a new <update> entry to this file automatically.
-->
<updates> <updates>
<update> <update>
<name>{{EXTENSION_NAME}}</name> <name>{{EXTENSION_NAME}}</name>
@@ -48,12 +12,14 @@ NOTE: Dual-platform: Gitea (primary) and GitHub (backup mirror).
<type>{{EXTENSION_TYPE}}</type> <type>{{EXTENSION_TYPE}}</type>
<version>{{VERSION}}</version> <version>{{VERSION}}</version>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/{{GITEA_ORG}}/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip</downloadurl> <downloadurl type="full" format="zip">
<downloadurl type="full" format="zip">https://github.com/{{GITHUB_ORG}}/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip</downloadurl> https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip
</downloadurl>
<downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip
</downloadurl>
</downloads> </downloads>
<targetplatform name="joomla" version="((5\.[0-9])|(6\.[0-9]))" /> <targetplatform name="joomla" version="[56].*"/>
<php_minimum>8.2</php_minimum> <php_minimum>8.1</php_minimum>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>{{MAINTAINER_URL}}</maintainerurl>
</update> </update>
</updates> </updates>
+41 -3
View File
@@ -139,11 +139,11 @@ The workflows are organized by platform and purpose:
Workflow templates specifically designed for Joomla extensions (components, modules, plugins, libraries, packages, templates): Workflow templates specifically designed for Joomla extensions (components, modules, plugins, libraries, packages, templates):
- **auto-release.yml.template** - Build & release pipeline for stable releases (triggers on merge to main). Reads version from README.md, sets platform version, builds ZIP+tar.gz packages, creates/updates Gitea releases, updates only the **stable** channel in updates.xml, and mirrors to GitHub. All patches release (including patch 00).
- **update-server.yml.template** - Multi-channel Joomla update server (triggers on dev/alpha/beta/rc branch merges). Builds packages, uploads to per-channel Gitea releases, updates only the **matching stability channel** in updates.xml via targeted Python regex replacement. Supports SFTP deploy to dev server.
- **ci-joomla.yml.template** - Continuous integration workflow with PHP validation, XML checking, and manifest verification - **ci-joomla.yml.template** - Continuous integration workflow with PHP validation, XML checking, and manifest verification
- **test.yml.template** - Comprehensive testing with PHPUnit, code quality checks, and integration tests - **deploy-manual.yml.template** - Manual deployment workflow for Joomla extensions with release triggering
- **release.yml.template** - Automated release workflow for creating and publishing Joomla extension packages
- **repo_health.yml.template** - Repository health monitoring including documentation checks and standards validation - **repo_health.yml.template** - Repository health monitoring including documentation checks and standards validation
- **version_branch.yml.template** - Automated version branch management and release preparation
### Dolibarr Templates (`dolibarr/`) ### Dolibarr Templates (`dolibarr/`)
@@ -474,6 +474,44 @@ Project-specific validation workflows using the MokoStandards plugin system. Eac
- Artifact upload for validation results - Artifact upload for validation results
- Proper exit codes (0=success, 1=failure, 2=error) - Proper exit codes (0=success, 1=failure, 2=error)
## Multi-Channel updates.xml Architecture (Joomla)
Joomla extensions use `updates.xml` as their update server manifest. MokoStandards implements a **multi-channel** architecture where each stability level has its own `<update>` block:
```xml
<updates>
<!-- 1. DEVELOPMENT --> <update>...<tag>development</tag>...</update>
<!-- 2. ALPHA --> <update>...<tag>alpha</tag>...</update>
<!-- 3. BETA --> <update>...<tag>beta</tag>...</update>
<!-- 4. RC --> <update>...<tag>rc</tag>...</update>
<!-- 5. STABLE --> <update>...<tag>stable</tag>...</update>
</updates>
```
### Key Principles
- **Cascading channel updates** — Each release updates its own channel and all lower stability channels. This ensures every site sees the update regardless of their minimum stability setting:
- `stable` → updates development, alpha, beta, rc, stable
- `rc` → updates development, alpha, beta, rc
- `beta` → updates development, alpha, beta
- `alpha` → updates development, alpha
- `development` → updates development only
- **Joomla filters by user setting** — Site administrators choose "Minimum Stability" in Joomla's update settings; Joomla shows updates matching that level or higher
- **SHA-256 must be raw hex** — No `sha256:` prefix. Joomla expects the raw hash value
- **Version format is zero-padded semver** — `XX.YY.ZZ` (e.g., `01.00.00`), not tag names like `v01`
- **All patches release** — Including patch `00`. Release workflows auto-bump the patch version before building (increments `XX.YY.ZZ``XX.YY.(ZZ+1)` in README.md, manifest, and the matching updates.xml channel)
- **Auto-bump on all branches** — `release.yml` (manual dispatch) and `update-server.yml` (branch merges) both auto-bump patch before building. No manual version bump required
### Channel-to-Workflow Mapping
| Channel | Workflow | Trigger | Release Tag |
|---------|----------|---------|-------------|
| `stable` | `auto-release.yml` | Merge PR to `main` | `stable` |
| `development` | `update-server.yml` | Merge PR to `dev/**` | `development` |
| `alpha` | `update-server.yml` | Merge PR to `alpha/**` | `alpha` |
| `beta` | `update-server.yml` | Merge PR to `beta/**` | `beta` |
| `rc` | `update-server.yml` | Merge PR to `rc/**` | `release-candidate` |
## Usage ## Usage
### For New Projects ### For New Projects
@@ -99,7 +99,7 @@ jobs:
# Hardcoded authorized users — always allowed # Hardcoded authorized users — always allowed
case "$ACTOR" in case "$ACTOR" in
jmiller-moko|github-actions\[bot\]) jmiller|gitea-actions[bot])
ALLOWED=true ALLOWED=true
PERMISSION=admin PERMISSION=admin
METHOD="hardcoded allowlist" METHOD="hardcoded allowlist"
+1 -1
View File
@@ -293,7 +293,7 @@ jobs:
# Search for an existing health-check issue (any state) # Search for an existing health-check issue (any state)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=updated&direction=desc" 2>/dev/null \ EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=updated&direction=desc" 2>/dev/null \
--jq '.[0].number' 2>/dev/null) 2>/dev/null | jq -r '.[0].number')
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
# Check if it's closed — reopen if so # Check if it's closed — reopen if so
@@ -26,7 +26,7 @@
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml | # | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
# | | # | |
# | Every version change: archives main -> version/XX.YY branch | # | Every version change: archives main -> version/XX.YY branch |
# | Patch 00 = development (no release). First release = patch 01. | # | All patches release (including 00). Patch 00/01 = full pipeline. |
# | First release only (patch == 01): | # | First release only (patch == 01): |
# | 7b. Create new Gitea Release | # | 7b. Create new Gitea Release |
# | | # | |
@@ -73,9 +73,9 @@ jobs:
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
run: | run: |
git clone --depth 1 --branch {{standards_branch}} --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api /tmp/mokostandards-api
cd /tmp/mokostandards-api cd /tmp/mokostandards-api
@@ -102,22 +102,15 @@ jobs:
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT" echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "major=$MAJOR" >> "$GITHUB_OUTPUT" echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" echo "release_tag=stable" >> "$GITHUB_OUTPUT"
# Determine stability for mirror gating
echo "stability=stable" >> "$GITHUB_OUTPUT" echo "stability=stable" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "00" ]; then echo "skip=false" >> "$GITHUB_OUTPUT"
echo "skip=true" >> "$GITHUB_OUTPUT" if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
echo "is_minor=false" >> "$GITHUB_OUTPUT" echo "is_minor=true" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (patch 00 = development — skipping release)" echo "Version: $VERSION (first release for this minor — full pipeline)"
else else
echo "skip=false" >> "$GITHUB_OUTPUT" echo "is_minor=false" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "01" ]; then echo "Version: $VERSION (patch — platform version + badges only)"
echo "is_minor=true" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (first release — full pipeline)"
else
echo "is_minor=false" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (patch — platform version + badges only)"
fi
fi fi
- name: Check if already released - name: Check if already released
@@ -296,9 +289,15 @@ jobs:
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component" [ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Templates/modules don't have <element> — derive from <name> (lowercased) # Derive element if not in manifest:
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas)
# 2. Fall back to repo name (lowercased)
if [ -z "$EXT_ELEMENT" ]; then if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ') EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
# If filename is generic (templateDetails, manifest), use repo name
case "$EXT_ELEMENT" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi fi
# Build client tag: plugins and frontend modules need <client>site</client> # Build client tag: plugins and frontend modules need <client>site</client>
@@ -326,11 +325,12 @@ jobs:
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>" PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi fi
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/v${VERSION}" INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
# -- Build stable entry to temp file # -- Build update entry for a given stability tag
{ build_entry() {
local TAG_NAME="$1"
printf '%s\n' ' <update>' printf '%s\n' ' <update>'
printf '%s\n' " <name>${EXT_NAME}</name>" printf '%s\n' " <name>${EXT_NAME}</name>"
printf '%s\n' " <description>${EXT_NAME} update</description>" printf '%s\n' " <description>${EXT_NAME} update</description>"
@@ -339,9 +339,7 @@ jobs:
printf '%s\n' " <version>${VERSION}</version>" printf '%s\n' " <version>${VERSION}</version>"
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
printf '%s\n' ' <tags>' printf '%s\n' " <tags><tag>${TAG_NAME}</tag></tags>"
printf '%s\n' ' <tag>stable</tag>'
printf '%s\n' ' </tags>'
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>" printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
printf '%s\n' ' <downloads>' printf '%s\n' ' <downloads>'
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>" printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
@@ -351,34 +349,27 @@ jobs:
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>' printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>' printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
printf '%s\n' ' </update>' printf '%s\n' ' </update>'
} > /tmp/stable_entry.xml }
# -- Write updates.xml preserving dev/rc entries
# Extract existing entries for other stability levels
if [ -f "updates.xml" ]; then
printf 'import re, sys\n' > /tmp/extract.py
printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
printf 'tag = sys.argv[1]\n' >> /tmp/extract.py
printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
fi
DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true)
BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true)
RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
# -- Write updates.xml with cascading channels
# Stable release updates ALL channels (development, alpha, beta, rc, stable)
{ {
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
printf '%s\n' " VERSION: ${VERSION}"
printf '%s\n' " -->"
printf '%s\n' ""
printf '%s\n' '<updates>' printf '%s\n' '<updates>'
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" build_entry "development"
[ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY" build_entry "alpha"
[ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY" build_entry "beta"
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" build_entry "rc"
cat /tmp/stable_entry.xml build_entry "stable"
printf '%s\n' '</updates>' printf '%s\n' '</updates>'
} > updates.xml } > updates.xml
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY
# -- Commit all changes --------------------------------------------------- # -- Commit all changes ---------------------------------------------------
- name: Commit release changes - name: Commit release changes
@@ -393,10 +384,12 @@ jobs:
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "gitea-actions[bot]"
# Set push URL with token for branch-protected repos
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A git add -A
git commit -m "chore(release): build ${VERSION} [skip ci]" \ git commit -m "chore(release): build ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" --author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push git push -u origin HEAD
# -- STEP 6: Create tag --------------------------------------------------- # -- STEP 6: Create tag ---------------------------------------------------
- name: "Step 6: Create git tag" - name: "Step 6: Create git tag"
@@ -577,9 +570,9 @@ jobs:
block = re.sub(r' <downloads>.*?</downloads>', new_downloads, block, flags=re.DOTALL) block = re.sub(r' <downloads>.*?</downloads>', new_downloads, block, flags=re.DOTALL)
# Add or replace sha256 # Add or replace sha256
if '<sha256>' in block: if '<sha256>' in block:
block = re.sub(r' <sha256>.*?</sha256>', f' <sha256>sha256:{sha}</sha256>', block) block = re.sub(r' <sha256>.*?</sha256>', f' <sha256>{sha}</sha256>', block)
else: else:
block = block.replace('</downloads>', f'</downloads>\n <sha256>sha256:{sha}</sha256>') block = block.replace('</downloads>', f'</downloads>\n <sha256>{sha}</sha256>')
return block return block
content = re.sub( content = re.sub(
@@ -593,10 +586,76 @@ jobs:
f.write(content) f.write(content)
PYEOF PYEOF
CURRENT_BRANCH="${{ github.ref_name }}"
git add updates.xml git add updates.xml
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \ git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true --author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
git push || true git push || true
# Sync updates.xml to main via PR (respects branch protection)
if [ "$CURRENT_BRANCH" != "main" ]; then
GA_TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}"
PR_BRANCH="chore/update-xml-${VERSION}"
# Create branch from main
MAIN_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches/main" | jq -r '.commit.sha // empty')
if [ -n "$MAIN_SHA" ]; then
curl -sf -X POST -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/branches" \
-d "$(jq -n --arg name "$PR_BRANCH" --arg sha "$MAIN_SHA" \
'{new_branch_name: $name, old_branch_name: "main"}')" > /dev/null 2>&1 || true
# Update updates.xml on the PR branch
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/contents/updates.xml?ref=${PR_BRANCH}" | jq -r '.sha // empty')
if [ -n "$FILE_SHA" ]; then
CONTENT=$(base64 -w0 updates.xml)
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/contents/updates.xml" \
-d "$(jq -n \
--arg content "$CONTENT" \
--arg sha "$FILE_SHA" \
--arg msg "chore: update stable channel to ${VERSION} [skip ci]" \
--arg branch "$PR_BRANCH" \
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
)" > /dev/null 2>&1
# Create PR
PR_URL=$(curl -sf -X POST -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/pulls" \
-d "$(jq -n \
--arg title "chore: update updates.xml for ${VERSION} [skip ci]" \
--arg head "$PR_BRANCH" \
--arg base "main" \
--arg body "Auto-generated by release workflow. Updates updates.xml with SHA-256 and download URLs for ${VERSION}." \
'{title: $title, head: $head, base: $base, body: $body}'
)" | jq -r '.number // empty')
# Auto-merge the PR
if [ -n "$PR_URL" ]; then
curl -sf -X POST -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/pulls/${PR_URL}/merge" \
-d '{"Do":"merge","merge_message_field":"chore: update updates.xml for '"${VERSION}"' [skip ci]"}' > /dev/null 2>&1 \
&& echo "updates.xml synced to main via PR #${PR_URL}" \
|| echo "PR #${PR_URL} created but auto-merge failed — merge manually"
# Cleanup: delete PR branch
curl -sf -X DELETE -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches/${PR_BRANCH}" > /dev/null 2>&1 || true
else
echo "WARNING: failed to create PR for updates.xml sync"
fi
fi
fi
fi
fi fi
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
@@ -613,10 +672,10 @@ jobs:
if: >- if: >-
steps.version.outputs.skip != 'true' && steps.version.outputs.skip != 'true' &&
steps.version.outputs.stability == 'stable' && steps.version.outputs.stability == 'stable' &&
secrets.GH_MIRROR_TOKEN != '' secrets.GH_TOKEN != ''
continue-on-error: true continue-on-error: true
env: env:
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: | run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
@@ -48,7 +48,7 @@ jobs:
MOKO_CLONE_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' }} MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: | run: |
git clone --depth 1 --branch {{standards_branch}} --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api /tmp/mokostandards-api
@@ -9,7 +9,7 @@
# PATH: /templates/workflows/joomla/deploy-manual.yml.template # PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.06.00 # VERSION: 04.06.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos # BRIEF: Manual SFTP deploy to dev server for Joomla repos
# NOTE: Joomla repos use update.xml for distribution. This is for manual # NOTE: Joomla repos use updates.xml for distribution. This is for manual
# dev server testing only — triggered via workflow_dispatch. # dev server testing only — triggered via workflow_dispatch.
name: Deploy to Dev (Manual) name: Deploy to Dev (Manual)
@@ -49,7 +49,7 @@ jobs:
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} 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 }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
run: | run: |
git clone --depth 1 --branch {{standards_branch}} --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true /tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
+5 -5
View File
@@ -6,11 +6,11 @@ This directory contains GitHub Actions workflow templates specifically designed
## Available Templates ## Available Templates
- **ci.yml** - Continuous integration workflow with PHP validation, XML checking, and manifest verification - **auto-release.yml.template** - Build & release pipeline for stable releases on merge to main. Reads version from README.md, builds ZIP+tar.gz, creates/updates Gitea release, updates only the stable channel in updates.xml. Mirrors to GitHub for stable releases.
- **test.yml** - Comprehensive testing workflow with PHPUnit, code quality checks, and integration tests - **update-server.yml.template** - Multi-channel Joomla update server. Triggers on dev/alpha/beta/rc branch merges, builds packages, uploads to per-channel Gitea releases, updates only the matching stability channel in updates.xml. All patches release (including patch 00).
- **release.yml** - Automated release workflow for creating and publishing Joomla extension packages - **ci-joomla.yml.template** - Continuous integration workflow with PHP validation, XML checking, and manifest verification
- **repo_health.yml** - Repository health monitoring including documentation checks and standards validation - **deploy-manual.yml.template** - Manual deployment workflow for Joomla extensions
- **version_branch.yml** - Automated version branch management and release preparation - **repo_health.yml.template** - Repository health monitoring including documentation checks and standards validation
## Metadata ## Metadata
@@ -99,7 +99,7 @@ jobs:
# Hardcoded authorized users — always allowed # Hardcoded authorized users — always allowed
case "$ACTOR" in case "$ACTOR" in
jmiller-moko|github-actions\[bot\]) jmiller|gitea-actions[bot])
ALLOWED=true ALLOWED=true
PERMISSION=admin PERMISSION=admin
METHOD="hardcoded allowlist" METHOD="hardcoded allowlist"
@@ -71,9 +71,9 @@ jobs:
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
run: | run: |
git clone --depth 1 --branch {{standards_branch}} --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true /tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
@@ -88,18 +88,16 @@ jobs:
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually) # Auto-bump patch on all branches (dev, alpha, beta, rc)
if [[ "$BRANCH" != dev/* ]]; then git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.name "gitea-actions[bot]"
git config --local user.name "gitea-actions[bot]" BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true) if [ -n "$BUMPED" ]; then
if [ -n "$BUMPED" ]; then VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") git add -A
git add -A git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ --author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true git push 2>/dev/null || true
git push 2>/dev/null || true
fi
fi fi
# Determine stability from branch or input # Determine stability from branch or input
@@ -140,9 +138,12 @@ jobs:
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component" [ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Templates and modules don't have <element> — derive from <name> # Derive element if not in manifest: try XML filename, then repo name
if [ -z "$EXT_ELEMENT" ]; then if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ') EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi fi
# Use manifest version if README version is empty # Use manifest version if README version is empty
@@ -274,7 +275,7 @@ jobs:
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n" NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n" NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n"
NEW_ENTRY="${NEW_ENTRY} </downloads>\n" NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n" [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n" NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n" [ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n" NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
@@ -285,23 +286,50 @@ jobs:
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# -- Merge into updates.xml (only update this stability channel) - # -- Merge into updates.xml (only update this stability channel) -
# Cascading update: each stability level updates itself and all lower levels
# stable → all | rc → rc,beta,alpha,dev | beta → beta,alpha,dev | alpha → alpha,dev | dev → dev
CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
TARGETS=""
for entry in $CASCADE_MAP; do
key="${entry%%:*}"
vals="${entry#*:}"
if [ "$key" = "${STABILITY}" ]; then
TARGETS="$vals"
break
fi
done
[ -z "$TARGETS" ] && TARGETS="${STABILITY}"
if [ ! -f "updates.xml" ]; then if [ ! -f "updates.xml" ]; then
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>" >> updates.xml
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later" >> updates.xml
printf '%s\n' " VERSION: ${VERSION}" >> updates.xml
printf '%s\n' " -->" >> updates.xml
printf '%s\n' "" >> updates.xml
printf '%s\n' '<updates>' >> updates.xml printf '%s\n' '<updates>' >> updates.xml
cat /tmp/new_entry.xml >> updates.xml cat /tmp/new_entry.xml >> updates.xml
printf '\n%s\n' '</updates>' >> updates.xml printf '\n%s\n' '</updates>' >> updates.xml
else else
# Remove existing entry for this stability, insert new one # Replace each cascading channel with the new entry (different tag)
export PY_TARGETS="$TARGETS"
python3 << PYEOF python3 << PYEOF
import re import re, os
targets = os.environ["PY_TARGETS"].split(",")
stability = "${STABILITY}" stability = "${STABILITY}"
with open("updates.xml") as f: with open("updates.xml") as f:
content = f.read() content = f.read()
with open("/tmp/new_entry.xml") as f: with open("/tmp/new_entry.xml") as f:
new_entry = f.read() new_entry_template = f.read()
pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\n?" for tag in targets:
content = re.sub(pattern, "", content, flags=re.DOTALL) tag = tag.strip()
content = content.replace("</updates>", new_entry + "\n</updates>") # Build entry with this tag
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
# Remove existing entry for this tag
pattern = r" <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>\n?"
content = re.sub(pattern, "", content, flags=re.DOTALL)
# Insert before </updates>
content = content.replace("</updates>", new_entry + "\n</updates>")
content = re.sub(r"\n{3,}", "\n\n", content) content = re.sub(r"\n{3,}", "\n\n", content)
with open("updates.xml", "w") as f: with open("updates.xml", "w") as f:
f.write(content) f.write(content)
@@ -309,7 +337,12 @@ jobs:
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
# Fallback: rebuild keeping other stability entries # Fallback: rebuild keeping other stability entries
{ {
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
printf '%s\n' " VERSION: ${VERSION}"
printf '%s\n' " -->"
printf '%s\n' ""
printf '%s\n' '<updates>' printf '%s\n' '<updates>'
for TAG in stable rc development; do for TAG in stable rc development; do
[ "$TAG" = "${STABILITY}" ] && continue [ "$TAG" = "${STABILITY}" ] && continue
@@ -338,10 +371,10 @@ jobs:
- name: Mirror release to GitHub - name: Mirror release to GitHub
if: >- if: >-
(steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') && (steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') &&
secrets.GH_MIRROR_TOKEN != '' secrets.GH_TOKEN != ''
continue-on-error: true continue-on-error: true
env: env:
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: | run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
STABILITY="${{ steps.update.outputs.stability }}" STABILITY="${{ steps.update.outputs.stability }}"
@@ -7,7 +7,7 @@
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /.github/workflows/auto-assign.yml # PATH: /.github/workflows/auto-assign.yml
# VERSION: 04.06.00 # VERSION: 04.06.00
# BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes # BRIEF: Auto-assign jmiller to unassigned issues and PRs every 15 minutes
name: Auto-Assign Issues & PRs name: Auto-Assign Issues & PRs
@@ -35,7 +35,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
run: | run: |
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
ASSIGNEE="jmiller-moko" ASSIGNEE="jmiller"
echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
@@ -44,10 +44,10 @@ jobs:
ASSIGNED_PRS=0 ASSIGNED_PRS=0
# Assign unassigned open issues # Assign unassigned open issues
ISSUES=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues?state=open&per_page=100&assignee=none" --jq '.[].number' 2>/dev/null || true) ISSUES=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues?state=open&per_page=100&assignee=none" 2>/dev/null | jq -r '.[].number' || true)
for NUM in $ISSUES; do for NUM in $ISSUES; do
# Skip PRs (the issues endpoint returns PRs too) # Skip PRs (the issues endpoint returns PRs too)
IS_PR=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM" --jq '.pull_request // empty' 2>/dev/null || true) IS_PR=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM" 2>/dev/null | jq -r '.pull_request // empty' || true)
if [ -z "$IS_PR" ]; then if [ -z "$IS_PR" ]; then
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && { curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1)) ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1))
@@ -57,7 +57,7 @@ jobs:
done done
# Assign unassigned open PRs # Assign unassigned open PRs
PRS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/pulls?state=open&per_page=100" --jq '.[] | select(.assignees | length == 0) | .number' 2>/dev/null || true) PRS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/pulls?state=open&per_page=100" 2>/dev/null | jq -r '.[] | select(.assignees | length == 0) | .number' || true)
for NUM in $PRS; do for NUM in $PRS; do
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && { curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
ASSIGNED_PRS=$((ASSIGNED_PRS + 1)) ASSIGNED_PRS=$((ASSIGNED_PRS + 1))
@@ -135,7 +135,7 @@ jobs:
--title "$SUB_FULL_TITLE" \ --title "$SUB_FULL_TITLE" \
--body "$SUB_BODY" \ --body "$SUB_BODY" \
--label "${SUB_LABELS}" \ --label "${SUB_LABELS}" \
--assignee "jmiller-moko" 2>&1) --assignee "jmiller" 2>&1)
SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$') SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$')
if [ -n "$SUB_NUM" ]; then if [ -n "$SUB_NUM" ]; then
@@ -154,7 +154,7 @@ jobs:
--title "$TITLE" \ --title "$TITLE" \
--body "$PARENT_BODY" \ --body "$PARENT_BODY" \
--label "${LABEL_TYPE},version" \ --label "${LABEL_TYPE},version" \
--assignee "jmiller-moko" 2>&1) --assignee "jmiller" 2>&1)
PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$') PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
@@ -44,9 +44,10 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
run: | run: |
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
--jq '.permission' 2>/dev/null || echo "read") 2>/dev/null | jq -r '.permission' || echo "read")
if [ "$PERMISSION" != "admin" ]; then if [ "$PERMISSION" != "admin" ]; then
echo "Denied: only admins can freeze/unfreeze branches (${ACTOR} has ${PERMISSION})" echo "Denied: only admins can freeze/unfreeze branches (${ACTOR} has ${PERMISSION})"
exit 1 exit 1
@@ -80,7 +81,7 @@ jobs:
printf '"conditions":{"ref_name":{"include":["refs/heads/%s"],"exclude":[]}},' "${BRANCH}" >> /tmp/ruleset.json printf '"conditions":{"ref_name":{"include":["refs/heads/%s"],"exclude":[]}},' "${BRANCH}" >> /tmp/ruleset.json
printf '"rules":[{"type":"update"},{"type":"deletion"},{"type":"non_fast_forward"}]}' >> /tmp/ruleset.json printf '"rules":[{"type":"update"},{"type":"deletion"},{"type":"non_fast_forward"}]}' >> /tmp/ruleset.json
RESULT=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets" 2>/dev/null -X POST --input /tmp/ruleset.json --jq '.id' 2>&1) || true RESULT=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets" 2>/dev/null -X POST -d @/tmp/ruleset.json 2>&1 | jq -r '.id') || true
if echo "$RESULT" | grep -qE '^[0-9]+$'; then if echo "$RESULT" | grep -qE '^[0-9]+$'; then
echo "Frozen \`${BRANCH}\` — ruleset #${RESULT}" >> $GITHUB_STEP_SUMMARY echo "Frozen \`${BRANCH}\` — ruleset #${RESULT}" >> $GITHUB_STEP_SUMMARY
@@ -79,6 +79,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
run: | run: |
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
ORG="${{ github.repository_owner }}" ORG="${{ github.repository_owner }}"
@@ -86,7 +87,7 @@ jobs:
AUTHORIZED="false" AUTHORIZED="false"
# Hardcoded authorized users — always allowed to deploy # Hardcoded authorized users — always allowed to deploy
AUTHORIZED_USERS="jmiller-moko gitea-actions[bot]" AUTHORIZED_USERS="jmiller gitea-actions[bot]"
for user in $AUTHORIZED_USERS; do for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then if [ "$ACTOR" = "$user" ]; then
AUTHORIZED="true" AUTHORIZED="true"
@@ -99,12 +100,12 @@ jobs:
# For other actors, check repo/org permissions via API # For other actors, check repo/org permissions via API
if [ "$AUTHORIZED" != "true" ]; then if [ "$AUTHORIZED" != "true" ]; then
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
--jq '.permission' 2>/dev/null) 2>/dev/null | jq -r '.permission')
METHOD="repo collaborator API" METHOD="repo collaborator API"
if [ -z "$PERMISSION" ]; then if [ -z "$PERMISSION" ]; then
ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \ ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \
--jq '.role' 2>/dev/null) 2>/dev/null | jq -r '.role')
METHOD="org membership API" METHOD="org membership API"
if [ "$ORG_ROLE" = "owner" ]; then if [ "$ORG_ROLE" = "owner" ]; then
PERMISSION="admin" PERMISSION="admin"
@@ -637,6 +638,7 @@ jobs:
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}"
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
BRANCH="${{ github.ref_name }}" BRANCH="${{ github.ref_name }}"
EVENT="${{ github.event_name }}" EVENT="${{ github.event_name }}"
NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC') NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
@@ -673,7 +675,7 @@ jobs:
# Look for an existing open deploy-failure issue # Look for an existing open deploy-failure issue
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" 2>/dev/null \ EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" 2>/dev/null \
--jq '.[0].number' 2>/dev/null) 2>/dev/null | jq -r '.[0].number')
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${EXISTING}" 2>/dev/null \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${EXISTING}" 2>/dev/null \
@@ -689,7 +691,7 @@ jobs:
--title "$TITLE" \ --title "$TITLE" \
--body "$BODY" \ --body "$BODY" \
--label "$LABEL" \ --label "$LABEL" \
--assignee "jmiller-moko" \ --assignee "jmiller" \
| tee -a "$GITHUB_STEP_SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
fi fi
@@ -83,6 +83,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
run: | run: |
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
ORG="${{ github.repository_owner }}" ORG="${{ github.repository_owner }}"
@@ -90,7 +91,7 @@ jobs:
AUTHORIZED="false" AUTHORIZED="false"
# Hardcoded authorized users — always allowed to deploy # Hardcoded authorized users — always allowed to deploy
AUTHORIZED_USERS="jmiller-moko gitea-actions[bot]" AUTHORIZED_USERS="jmiller gitea-actions[bot]"
for user in $AUTHORIZED_USERS; do for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then if [ "$ACTOR" = "$user" ]; then
AUTHORIZED="true" AUTHORIZED="true"
@@ -103,12 +104,12 @@ jobs:
# For other actors, check repo/org permissions via API # For other actors, check repo/org permissions via API
if [ "$AUTHORIZED" != "true" ]; then if [ "$AUTHORIZED" != "true" ]; then
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
--jq '.permission' 2>/dev/null) 2>/dev/null | jq -r '.permission')
METHOD="repo collaborator API" METHOD="repo collaborator API"
if [ -z "$PERMISSION" ]; then if [ -z "$PERMISSION" ]; then
ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \ ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \
--jq '.role' 2>/dev/null) 2>/dev/null | jq -r '.role')
METHOD="org membership API" METHOD="org membership API"
if [ "$ORG_ROLE" = "owner" ]; then if [ "$ORG_ROLE" = "owner" ]; then
PERMISSION="admin" PERMISSION="admin"
@@ -87,6 +87,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
run: | run: |
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
ORG="${{ github.repository_owner }}" ORG="${{ github.repository_owner }}"
@@ -94,7 +95,7 @@ jobs:
AUTHORIZED="false" AUTHORIZED="false"
# Hardcoded authorized users — always allowed to deploy # Hardcoded authorized users — always allowed to deploy
AUTHORIZED_USERS="jmiller-moko gitea-actions[bot]" AUTHORIZED_USERS="jmiller gitea-actions[bot]"
for user in $AUTHORIZED_USERS; do for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then if [ "$ACTOR" = "$user" ]; then
AUTHORIZED="true" AUTHORIZED="true"
@@ -107,12 +108,12 @@ jobs:
# For other actors, check repo/org permissions via API # For other actors, check repo/org permissions via API
if [ "$AUTHORIZED" != "true" ]; then if [ "$AUTHORIZED" != "true" ]; then
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
--jq '.permission' 2>/dev/null) 2>/dev/null | jq -r '.permission')
METHOD="repo collaborator API" METHOD="repo collaborator API"
if [ -z "$PERMISSION" ]; then if [ -z "$PERMISSION" ]; then
ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \ ORG_ROLE=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${ORG}/memberships/${ACTOR}" \
--jq '.role' 2>/dev/null) 2>/dev/null | jq -r '.role')
METHOD="org membership API" METHOD="org membership API"
if [ "$ORG_ROLE" = "owner" ]; then if [ "$ORG_ROLE" = "owner" ]; then
PERMISSION="admin" PERMISSION="admin"
@@ -579,6 +580,7 @@ jobs:
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}"
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
ALLOWED_USERS="jmiller gitea-actions[bot]"
BRANCH="${{ github.ref_name }}" BRANCH="${{ github.ref_name }}"
EVENT="${{ github.event_name }}" EVENT="${{ github.event_name }}"
NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC') NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
@@ -615,7 +617,7 @@ jobs:
# Look for an existing deploy-failure issue (any state — reopen if closed) # Look for an existing deploy-failure issue (any state — reopen if closed)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" 2>/dev/null \ EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" 2>/dev/null \
--jq '.[0].number' 2>/dev/null) 2>/dev/null | jq -r '.[0].number')
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${EXISTING}" 2>/dev/null \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${EXISTING}" 2>/dev/null \
@@ -631,7 +633,7 @@ jobs:
--title "$TITLE" \ --title "$TITLE" \
--body "$BODY" \ --body "$BODY" \
--label "$LABEL" \ --label "$LABEL" \
--assignee "jmiller-moko" \ --assignee "jmiller" \
| tee -a "$GITHUB_STEP_SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
fi fi
@@ -0,0 +1,339 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards-API.Deployment
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/shared/export-mysql.yml.template
# VERSION: 04.06.12
# BRIEF: Export MySQL database from dev/demo server and save as artifact or commit
name: Export MySQL Database
on:
workflow_dispatch:
inputs:
environment:
description: 'Which server to export from'
required: true
type: choice
options:
- dev
- demo
default: 'dev'
database:
description: 'Database name (overrides variable)'
required: false
type: string
default: ''
save_to_repo:
description: 'Commit SQL dump to repo (otherwise artifact only)'
required: false
type: boolean
default: false
branch:
description: 'Branch to commit to (if save_to_repo)'
required: false
type: string
default: 'dev'
# ──────────────────────────────────────────────────────────────
# Required secrets and variables (per environment):
#
# DEV ENVIRONMENT — secrets/variables:
# DEV_SSH_HOST — Dev server hostname
# DEV_SSH_PORT — SSH port (default: 22)
# DEV_SSH_USERNAME — SSH user
# DEV_SSH_KEY — SSH private key
# DEV_PULL_PATH — Remote install path (repo variable)
#
# DEMO ENVIRONMENT — secrets/variables:
# DEMO_FTP_HOST — Demo server hostname (reused from deploy)
# DEMO_FTP_PORT — SSH port (reused, default: 22)
# DEMO_FTP_USERNAME — SSH user (reused from deploy)
# DEMO_FTP_KEY — SSH key (reused from deploy)
# DEMO_FTP_PATH — Remote install path (repo variable)
#
# MySQL credentials are read automatically from:
# Joomla: configuration.php ($user, $password, $db)
# Dolibarr: conf/conf.php ($dolibarr_main_db_user, etc.)
# No MySQL secrets needed — credentials come from the remote config file.
# ──────────────────────────────────────────────────────────────
permissions:
contents: write
jobs:
export-mysql:
name: Export MySQL — ${{ inputs.environment }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
if: inputs.save_to_repo
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: Resolve environment config
id: env
run: |
ENV="${{ inputs.environment }}"
if [ "$ENV" = "dev" ]; then
HOST="${{ vars.DEV_SSH_HOST }}"
PORT="${{ vars.DEV_SSH_PORT || '22' }}"
USER="${{ vars.DEV_SSH_USERNAME }}"
DB="${{ inputs.database || vars.DEV_MYSQL_DATABASE }}"
MYSQL_USER="${{ vars.DEV_MYSQL_USER || 'root' }}"
elif [ "$ENV" = "demo" ]; then
HOST="${{ vars.DEMO_FTP_HOST }}"
PORT="${{ vars.DEMO_FTP_PORT || '22' }}"
USER="${{ vars.DEMO_FTP_USERNAME }}"
DB="${{ inputs.database || vars.DEMO_MYSQL_DATABASE }}"
MYSQL_USER="${{ vars.DEMO_MYSQL_USER || 'root' }}"
fi
MISSING=""
[ -z "$HOST" ] && MISSING="${MISSING} ${ENV^^}_SSH_HOST"
[ -z "$USER" ] && MISSING="${MISSING} ${ENV^^}_SSH_USERNAME"
[ -z "$DB" ] && MISSING="${MISSING} ${ENV^^}_MYSQL_DATABASE"
if [ -n "$MISSING" ]; then
echo "ERROR: Missing variables:${MISSING}"
exit 1
fi
echo "host=${HOST}" >> $GITHUB_OUTPUT
echo "port=${PORT}" >> $GITHUB_OUTPUT
echo "user=${USER}" >> $GITHUB_OUTPUT
echo "database=${DB}" >> $GITHUB_OUTPUT
echo "mysql_user=${MYSQL_USER}" >> $GITHUB_OUTPUT
echo "Config OK — exporting ${DB} from ${USER}@${HOST}"
- name: Setup SSH
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ENV="${{ inputs.environment }}"
if [ "$ENV" = "dev" ]; then
KEY="${{ secrets.DEV_SSH_KEY }}"
else
KEY="${{ secrets.DEMO_FTP_KEY }}"
fi
if [ -n "$KEY" ]; then
echo "$KEY" > ~/.ssh/export_key
chmod 600 ~/.ssh/export_key
else
echo "ERROR: No SSH key found for ${ENV} environment"
exit 1
fi
echo "Host *" > ~/.ssh/config
echo " StrictHostKeyChecking no" >> ~/.ssh/config
echo " UserKnownHostsFile /dev/null" >> ~/.ssh/config
chmod 600 ~/.ssh/config
- name: Export MySQL database
id: export
run: |
HOST="${{ steps.env.outputs.host }}"
PORT="${{ steps.env.outputs.port }}"
USER="${{ steps.env.outputs.user }}"
DB="${{ steps.env.outputs.database }}"
ENV="${{ inputs.environment }}"
CONFIG_PATH="${{ vars.DEV_PULL_PATH || vars.DEMO_FTP_PATH }}"
# Read MySQL credentials from the remote config file
# Joomla: configuration.php → $user, $password, $db
# Dolibarr: conf/conf.php → $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name
echo "Reading MySQL credentials from remote config file..."
CREDS=$(ssh -p "${PORT}" -i ~/.ssh/export_key "${USER}@${HOST}" bash << 'SSHEOF'
# Try Joomla configuration.php first
for cfg in "{{CONFIG_PATH}}/configuration.php" "/var/www/html/configuration.php" "$(find /var/www -name 'configuration.php' -maxdepth 3 2>/dev/null | head -1)"; do
if [ -f "$cfg" ]; then
DB_USER=$(php -r "include '$cfg'; echo \$user ?? '';")
DB_PASS=$(php -r "include '$cfg'; echo \$password ?? '';")
DB_NAME=$(php -r "include '$cfg'; echo \$db ?? '';")
DB_HOST=$(php -r "include '$cfg'; echo \$host ?? 'localhost';")
if [ -n "$DB_USER" ] && [ -n "$DB_NAME" ]; then
echo "TYPE=joomla"
echo "DB_USER=$DB_USER"
echo "DB_PASS=$DB_PASS"
echo "DB_NAME=$DB_NAME"
echo "DB_HOST=$DB_HOST"
exit 0
fi
fi
done
# Try Dolibarr conf/conf.php
for cfg in "{{CONFIG_PATH}}/conf/conf.php" "/var/www/html/conf/conf.php" "$(find /var/www -name 'conf.php' -path '*/conf/*' -maxdepth 4 2>/dev/null | head -1)"; do
if [ -f "$cfg" ]; then
DB_USER=$(php -r "include '$cfg'; echo \$dolibarr_main_db_user ?? '';")
DB_PASS=$(php -r "include '$cfg'; echo \$dolibarr_main_db_pass ?? '';")
DB_NAME=$(php -r "include '$cfg'; echo \$dolibarr_main_db_name ?? '';")
DB_HOST=$(php -r "include '$cfg'; echo \$dolibarr_main_db_host ?? 'localhost';")
if [ -n "$DB_USER" ] && [ -n "$DB_NAME" ]; then
echo "TYPE=dolibarr"
echo "DB_USER=$DB_USER"
echo "DB_PASS=$DB_PASS"
echo "DB_NAME=$DB_NAME"
echo "DB_HOST=$DB_HOST"
exit 0
fi
fi
done
echo "TYPE=not_found"
SSHEOF
)
PLATFORM=$(echo "$CREDS" | grep "^TYPE=" | cut -d= -f2)
MYSQL_USER=$(echo "$CREDS" | grep "^DB_USER=" | cut -d= -f2-)
MYSQL_PASS=$(echo "$CREDS" | grep "^DB_PASS=" | cut -d= -f2-)
DB_NAME=$(echo "$CREDS" | grep "^DB_NAME=" | cut -d= -f2-)
DB_HOST_REMOTE=$(echo "$CREDS" | grep "^DB_HOST=" | cut -d= -f2-)
if [ "$PLATFORM" = "not_found" ]; then
echo "ERROR: Could not find Joomla configuration.php or Dolibarr conf/conf.php on remote server"
exit 1
fi
# Override DB name if explicitly provided
[ -n "$DB" ] && DB_NAME="$DB"
echo "Platform: ${PLATFORM}"
echo "Database: ${DB_NAME} (user: ${MYSQL_USER}, host: ${DB_HOST_REMOTE})"
TIMESTAMP=$(date -u +%Y%m%d_%H%M%S)
FILENAME="${DB_NAME}_${ENV}_${TIMESTAMP}.sql"
echo "Exporting ${DB_NAME} from ${HOST}..."
# Run mysqldump over SSH using credentials from config file
ssh -p "${PORT}" -i ~/.ssh/export_key "${USER}@${HOST}" \
"mysqldump --single-transaction --no-tablespaces --routines --triggers \
-h ${DB_HOST_REMOTE} -u ${MYSQL_USER} -p'${MYSQL_PASS}' ${DB_NAME}" \
> "/tmp/${FILENAME}" 2>/tmp/mysqldump.err
if [ $? -ne 0 ]; then
echo "ERROR: mysqldump failed"
cat /tmp/mysqldump.err
exit 1
fi
SIZE=$(du -h "/tmp/${FILENAME}" | cut -f1)
LINES=$(wc -l < "/tmp/${FILENAME}")
echo "Export complete: ${FILENAME} (${SIZE}, ${LINES} lines)"
# ── Sanitize PII / credentials ──────────────────────────
echo "Sanitizing sensitive data..."
SANITIZED="/tmp/${FILENAME%.sql}_sanitized.sql"
cp "/tmp/${FILENAME}" "$SANITIZED"
# Joomla sanitization
# - Clear user passwords (set to bcrypt hash of 'sanitized')
# - Clear session data
# - Clear user email addresses (keep admin)
# - Clear reset tokens
BCRYPT_SANITIZED='$2y$10$sanitized.sanitized.sanitized.sanitized.sanitized.sa'
sed -i \
-e "s/\(VALUES([^)]*,'[^']*','\)\$2[ayb]\$[^']*\('/\1${BCRYPT_SANITIZED}'/g" \
"$SANITIZED"
# Joomla: clear sessions table content
sed -i '/INSERT INTO.*_session/d' "$SANITIZED"
# Joomla: sanitize user emails (replace with user{id}@sanitized.local)
python3 -c "
import re, sys
with open('$SANITIZED', encoding='utf-8', errors='replace') as f:
content = f.read()
# Joomla users table: sanitize emails but keep structure
# Pattern: email addresses in INSERT statements for user tables
content = re.sub(
r\"(['\\\"])([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(['\\\"])\",
lambda m: m.group(1) + 'user@sanitized.local' + m.group(3)
if 'admin' not in m.group(2).lower() and 'moko' not in m.group(2).lower()
else m.group(0),
content
)
# Dolibarr: clear API keys, passwords, session tokens
content = re.sub(r\"'(api_key|pass_crypted|pass_temp|session_id|token)','[^']*'\", r\"'\1',''\", content)
# Dolibarr: clear LDAP passwords
content = re.sub(r\"'ldap_pass','[^']*'\", \"'ldap_pass',''\", content)
# Clear any raw passwords or tokens in conf values
content = re.sub(r\"'(smtp_pass|ftp_password|oauth_.*secret)','[^']*'\", r\"'\1','[SANITIZED]'\", content)
with open('$SANITIZED', 'w', encoding='utf-8') as f:
f.write(content)
print('Sanitization complete')
" 2>/dev/null || echo "Python sanitization skipped (no python3)"
mv "$SANITIZED" "/tmp/${FILENAME}"
echo "Sanitized: passwords, sessions, emails (admin/moko preserved)"
# Compress
gzip "/tmp/${FILENAME}"
GZ_SIZE=$(du -h "/tmp/${FILENAME}.gz" | cut -f1)
echo "Compressed: ${FILENAME}.gz (${GZ_SIZE})"
echo "filename=${FILENAME}" >> $GITHUB_OUTPUT
echo "gz_filename=${FILENAME}.gz" >> $GITHUB_OUTPUT
echo "size=${GZ_SIZE}" >> $GITHUB_OUTPUT
echo "lines=${LINES}" >> $GITHUB_OUTPUT
- name: Commit to repo
if: inputs.save_to_repo && steps.export.outputs.filename != ''
run: |
mkdir -p sql/exports
cp "/tmp/${{ steps.export.outputs.gz_filename }}" "sql/exports/"
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.git.mokoconsulting.tech"
git add sql/exports/
git commit -m "chore(db): export ${{ steps.env.outputs.database }} from ${{ inputs.environment }}
File: ${{ steps.export.outputs.gz_filename }}
Size: ${{ steps.export.outputs.size }}
Lines: ${{ steps.export.outputs.lines }}
Source: ${{ steps.env.outputs.user }}@${{ steps.env.outputs.host }}"
git push origin ${{ inputs.branch }}
echo "Committed to ${{ inputs.branch }}"
- name: Upload artifact
if: steps.export.outputs.filename != ''
uses: actions/upload-artifact@v4
with:
name: mysql-export-${{ inputs.environment }}-${{ steps.env.outputs.database }}
path: /tmp/${{ steps.export.outputs.gz_filename }}
retention-days: 30
- name: Summary
run: |
echo "## MySQL Export — ${{ inputs.environment }}"
echo ""
echo "- Database: \`${{ steps.env.outputs.database }}\`"
echo "- Server: \`${{ steps.env.outputs.host }}\`"
echo "- File: \`${{ steps.export.outputs.gz_filename }}\`"
echo "- Size: ${{ steps.export.outputs.size }}"
echo "- Lines: ${{ steps.export.outputs.lines }}"
if [ "${{ inputs.save_to_repo }}" = "true" ]; then
echo "- Saved to: \`sql/exports/\` on branch \`${{ inputs.branch }}\`"
else
echo "- Available as workflow artifact (30 day retention)"
fi
- name: Cleanup
if: always()
run: rm -f ~/.ssh/export_key /tmp/*.sql /tmp/*.sql.gz
@@ -0,0 +1,173 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards-API.Deployment
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/shared/pull-from-dev.yml.template
# VERSION: 04.06.12
# BRIEF: Download files from dev server into repo src/ directory
name: Pull from Dev Server
on:
workflow_dispatch:
inputs:
remote_path:
description: 'Remote path to download (overrides DEV_PULL_PATH variable)'
required: false
type: string
default: ''
target_dir:
description: 'Local directory to save to'
required: false
type: string
default: 'src'
branch:
description: 'Branch to commit to'
required: false
type: string
default: 'dev'
dry_run:
description: 'Preview only (no commit)'
required: false
type: boolean
default: true
# ──────────────────────────────────────────────────────────────
# Required secrets and variables:
#
# SECRETS (org or repo level):
# DEV_SSH_KEY — SSH private key for dev server access
# DEV_SSH_PASSWORD — OR password auth (if not using key)
#
# VARIABLES (org or repo level):
# DEV_SSH_HOST — Dev server hostname (e.g., dev.mokoconsulting.tech)
# DEV_SSH_PORT — SSH port (default: 22)
# DEV_SSH_USERNAME — SSH user
# DEV_PULL_PATH — Remote path to download (e.g., /var/www/html/plugins/system/mokojoomtos)
# ──────────────────────────────────────────────────────────────
permissions:
contents: write
jobs:
pull-from-dev:
name: Pull from Dev Server
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: Validate configuration
run: |
MISSING=""
[ -z "${{ vars.DEV_SSH_HOST }}" ] && MISSING="${MISSING} DEV_SSH_HOST"
[ -z "${{ vars.DEV_SSH_USERNAME }}" ] && MISSING="${MISSING} DEV_SSH_USERNAME"
REMOTE="${{ inputs.remote_path || vars.DEV_PULL_PATH }}"
[ -z "$REMOTE" ] && MISSING="${MISSING} DEV_PULL_PATH"
if [ -n "$MISSING" ]; then
echo "ERROR: Missing required variables:${MISSING}"
echo "Set these as org or repo variables in Gitea Actions settings."
exit 1
fi
echo "remote_path=${REMOTE}" >> $GITHUB_OUTPUT
echo "Config OK — pulling from ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }}:${REMOTE}"
id: config
- name: Setup SSH
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
if [ -n "${{ secrets.DEV_SSH_KEY }}" ]; then
echo "${{ secrets.DEV_SSH_KEY }}" > ~/.ssh/dev_key
chmod 600 ~/.ssh/dev_key
echo "Auth: SSH key"
else
echo "Auth: password (sshpass)"
sudo apt-get install -y sshpass -qq
fi
# Disable host key checking for automation
echo "Host *" > ~/.ssh/config
echo " StrictHostKeyChecking no" >> ~/.ssh/config
echo " UserKnownHostsFile /dev/null" >> ~/.ssh/config
chmod 600 ~/.ssh/config
- name: Download from dev server
id: download
run: |
HOST="${{ vars.DEV_SSH_HOST }}"
PORT="${{ vars.DEV_SSH_PORT || '22' }}"
USER="${{ vars.DEV_SSH_USERNAME }}"
REMOTE="${{ steps.config.outputs.remote_path }}"
LOCAL="${{ inputs.target_dir }}"
echo "Downloading: ${USER}@${HOST}:${REMOTE} → ${LOCAL}/"
# Build rsync command
SSH_CMD="ssh -p ${PORT}"
if [ -f ~/.ssh/dev_key ]; then
SSH_CMD="${SSH_CMD} -i ~/.ssh/dev_key"
fi
# Rsync from remote to local (mirror mode, delete extra local files)
rsync -avz --delete \
-e "${SSH_CMD}" \
"${USER}@${HOST}:${REMOTE}/" \
"${LOCAL}/" \
--exclude='.git' \
--exclude='.gitignore' \
--exclude='node_modules' \
--exclude='vendor' \
--exclude='cache' \
--exclude='tmp' \
--exclude='log' \
2>&1 | tee /tmp/rsync.log
CHANGED=$(git status --porcelain "${LOCAL}/" | wc -l)
echo "changed=${CHANGED}" >> $GITHUB_OUTPUT
echo "Files changed: ${CHANGED}"
- name: Show diff
if: steps.download.outputs.changed != '0'
run: |
echo "=== Changed files ==="
git status --short "${{ inputs.target_dir }}/"
echo ""
echo "=== Diff summary ==="
git diff --stat "${{ inputs.target_dir }}/"
- name: Commit and push
if: steps.download.outputs.changed != '0' && inputs.dry_run != true
run: |
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.git.mokoconsulting.tech"
git add "${{ inputs.target_dir }}/"
git commit -m "chore(sync): pull latest from dev server
Source: ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }}:${{ steps.config.outputs.remote_path }}
Files changed: ${{ steps.download.outputs.changed }}
Triggered by: ${{ gitea.actor }}"
git push origin ${{ inputs.branch }}
echo "Pushed to ${{ inputs.branch }}"
- name: Summary
run: |
echo "## Pull from Dev Server"
echo ""
if [ "${{ inputs.dry_run }}" = "true" ]; then
echo "**DRY RUN** — no changes committed"
fi
echo "- Source: \`${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }}:${{ steps.config.outputs.remote_path }}\`"
echo "- Target: \`${{ inputs.target_dir }}/\`"
echo "- Changed files: ${{ steps.download.outputs.changed }}"
- name: Cleanup
if: always()
run: rm -f ~/.ssh/dev_key
@@ -80,7 +80,7 @@ jobs:
echo "✅ Scheduled run — authorized" echo "✅ Scheduled run — authorized"
exit 0 exit 0
fi fi
AUTHORIZED_USERS="jmiller-moko gitea-actions[bot]" AUTHORIZED_USERS="jmiller gitea-actions[bot]"
for user in $AUTHORIZED_USERS; do for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then if [ "$ACTOR" = "$user" ]; then
echo "✅ ${ACTOR} authorized" echo "✅ ${ACTOR} authorized"
@@ -88,7 +88,7 @@ jobs:
fi fi
done done
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \ PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
--jq '.permission' 2>/dev/null) 2>/dev/null | jq -r '.permission')
case "$PERMISSION" in case "$PERMISSION" in
admin|maintain) echo "✅ ${ACTOR} has ${PERMISSION}" ;; admin|maintain) echo "✅ ${ACTOR} has ${PERMISSION}" ;;
*) echo "❌ Admin or maintain required"; exit 1 ;; *) echo "❌ Admin or maintain required"; exit 1 ;;
@@ -191,7 +191,7 @@ jobs:
echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels?per_page=100" 2>/dev/null --paginate --jq '.[].name' | while read -r label; do curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels?per_page=100" 2>/dev/null --jq '.[].name' | while read -r label; do
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))") ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))")
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels/${ENCODED}" 2>/dev/null || true curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels/${ENCODED}" 2>/dev/null || true
done done
@@ -278,7 +278,7 @@ jobs:
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/branches?per_page=100" | jq -r '.[].name' 2>/dev/null | \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/branches?per_page=100" | jq -r '.[].name' 2>/dev/null | \
grep "^chore/sync-mokostandards" | \ grep "^chore/sync-mokostandards" | \
grep -v "^${CURRENT}$" | while read -r branch; do grep -v "^${CURRENT}$" | while read -r branch; do
gh pr list --repo "$REPO" --head "$branch" --state open --json number --jq '.[].number' 2>/dev/null | while read -r pr; do gh pr list --repo "$REPO" --head "$branch" --state open --json number 2>/dev/null | jq -r '.[].number' | while read -r pr; do
gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true
echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY
done done
@@ -305,7 +305,7 @@ jobs:
# Delete cancelled and stale workflow runs # Delete cancelled and stale workflow runs
for status in cancelled stale; do for status in cancelled stale; do
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?status=${status}&per_page=100" 2>/dev/null \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?status=${status}&per_page=100" 2>/dev/null \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do 2>/dev/null | jq -r '.workflow_runs[].id' | while read -r run_id; do
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs/${run_id}" 2>/dev/null || true curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs/${run_id}" 2>/dev/null || true
DELETED=$((DELETED+1)) DELETED=$((DELETED+1))
done done
@@ -327,7 +327,7 @@ jobs:
DELETED=0 DELETED=0
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?created=<${CUTOFF}&per_page=100" 2>/dev/null \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?created=<${CUTOFF}&per_page=100" 2>/dev/null \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do 2>/dev/null | jq -r '.workflow_runs[].id' | while read -r run_id; do
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs/${run_id}/logs" 2>/dev/null || true curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs/${run_id}/logs" 2>/dev/null || true
DELETED=$((DELETED+1)) DELETED=$((DELETED+1))
done done
@@ -171,7 +171,7 @@ jobs:
title: `[Terraform Drift] ${env} environment has drifted`, title: `[Terraform Drift] ${env} environment has drifted`,
body: body, body: body,
labels: ['terraform-drift', `environment:${env}`, 'infrastructure'], labels: ['terraform-drift', `environment:${env}`, 'infrastructure'],
assignees: ['copilot', 'jmiller-moko'] assignees: ['copilot', 'jmiller']
}); });
} else { } else {
// Update existing issue // Update existing issue
@@ -209,7 +209,7 @@ jobs:
if [ "${{ inputs.target_repos }}" = "all" ]; then if [ "${{ inputs.target_repos }}" = "all" ]; then
# Get all org repositories # Get all org repositories
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${{ github.repository_owner }}/repos?limit=50" \ curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/orgs/${{ github.repository_owner }}/repos?limit=50" \
--jq '.[].name' > repos.txt | jq -r '.[].name' > repos.txt
else else
# Use specified repositories # Use specified repositories
echo "${{ inputs.target_repos }}" | tr ',' '\n' > repos.txt echo "${{ inputs.target_repos }}" | tr ',' '\n' > repos.txt
@@ -288,7 +288,7 @@ jobs:
title: 'Terraform Repository Template Update Plan', title: 'Terraform Repository Template Update Plan',
body: output, body: output,
labels: ['terraform', 'repository-management', 'automation'], labels: ['terraform', 'repository-management', 'automation'],
assignees: ['copilot', 'jmiller-moko'] assignees: ['copilot', 'jmiller']
}); });
- name: Terraform Apply - name: Terraform Apply
@@ -342,7 +342,7 @@ jobs:
title: `Repository Templates Updated - ${new Date().toISOString().split('T')[0]}`, title: `Repository Templates Updated - ${new Date().toISOString().split('T')[0]}`,
body: body, body: body,
labels: ['terraform', 'repository-management', 'completed'], labels: ['terraform', 'repository-management', 'completed'],
assignees: ['copilot', 'jmiller-moko'] assignees: ['copilot', 'jmiller']
}); });
- name: Summary - name: Summary
+1 -1
View File
@@ -64,7 +64,7 @@ class AutoDetectPlatform extends CLIApp
{ {
return [ return [
'repo-path:' => 'Path to repository to analyze (default: current directory)', 'repo-path:' => 'Path to repository to analyze (default: current directory)',
'schema-dir:' => 'Path to schema definitions directory (default: api/definitions/default)', 'schema-dir:' => 'Path to schema definitions directory (default: definitions/default)',
'output-dir:' => 'Directory for output reports (default: var/logs/validation)', 'output-dir:' => 'Directory for output reports (default: var/logs/validation)',
]; ];
} }
+3 -3
View File
@@ -15,9 +15,9 @@
* BRIEF: Validate composer.json enterprise dependency across all governed repos * BRIEF: Validate composer.json enterprise dependency across all governed repos
* *
* USAGE * USAGE
* php api/validate/check_composer_deps.php --repo MokoCRM # Single repo * php validate/check_composer_deps.php --repo MokoCRM # Single repo
* php api/validate/check_composer_deps.php --all # All repos * php validate/check_composer_deps.php --all # All repos
* php api/validate/check_composer_deps.php --all --json # JSON output * php validate/check_composer_deps.php --all --json # JSON output
*/ */
declare(strict_types=1); declare(strict_types=1);
+3 -3
View File
@@ -139,18 +139,18 @@ class EnterpriseReadinessChecker extends CliFramework
{ {
$required = ['ApiClient', 'AuditLogger', 'Config', 'ErrorRecovery', 'MetricsCollector']; $required = ['ApiClient', 'AuditLogger', 'Config', 'ErrorRecovery', 'MetricsCollector'];
// Enterprise libs may live in vendor/ (Composer install) or api/lib/Enterprise/ (MokoStandards itself). // Enterprise libs may live in vendor/ (Composer install) or lib/Enterprise/ (MokoStandards itself).
// A single vendor/ directory confirms the whole package is present — no need to check per-file. // A single vendor/ directory confirms the whole package is present — no need to check per-file.
$vendorPkg = "{$path}/vendor/mokoconsulting-tech/enterprise"; $vendorPkg = "{$path}/vendor/mokoconsulting-tech/enterprise";
$inVendor = is_dir($vendorPkg); $inVendor = is_dir($vendorPkg);
foreach ($required as $library) { foreach ($required as $library) {
$localFile = "{$path}/api/lib/Enterprise/{$library}.php"; $localFile = "{$path}/lib/Enterprise/{$library}.php";
$found = $inVendor || file_exists($localFile); $found = $inVendor || file_exists($localFile);
$this->addResult( $this->addResult(
"Enterprise library: {$library}", "Enterprise library: {$library}",
$found, $found,
"Missing enterprise library (not in vendor/mokoconsulting-tech/enterprise or api/lib/Enterprise/)" "Missing enterprise library (not in vendor/mokoconsulting-tech/enterprise or lib/Enterprise/)"
); );
} }
} }
+1 -1
View File
@@ -144,7 +144,7 @@ class CheckVersionConsistency extends CliFramework
// ── Check PHP Enterprise library files ──────────────────────────────── // ── Check PHP Enterprise library files ────────────────────────────────
$this->section('Checking PHP source files'); $this->section('Checking PHP source files');
$phpFiles = $this->findPhpFiles($path . '/api/lib/Enterprise'); $phpFiles = $this->findPhpFiles($path . '/lib/Enterprise');
$phpTotal = count($phpFiles); $phpTotal = count($phpFiles);
foreach ($phpFiles as $i => $file) { foreach ($phpFiles as $i => $file) {
+1 -1
View File
@@ -529,7 +529,7 @@ class DriftScanner extends CliFramework
$body .= "1. **Option 1:** Run bulk sync to update all files automatically\n"; $body .= "1. **Option 1:** Run bulk sync to update all files automatically\n";
$body .= " ```bash\n"; $body .= " ```bash\n";
$body .= " # From MokoStandards repository\n"; $body .= " # From MokoStandards repository\n";
$body .= " php api/automation/bulk_sync.php --repos=\"{$repo}\"\n"; $body .= " php automation/bulk_sync.php --repos=\"{$repo}\"\n";
$body .= " ```\n\n"; $body .= " ```\n\n";
$body .= "2. **Option 2:** If changes are intentional, update `.github/override.tf` to exclude files\n\n"; $body .= "2. **Option 2:** If changes are intentional, update `.github/override.tf` to exclude files\n\n";
$body .= "3. **Option 3:** Manually update files to match templates\n\n"; $body .= "3. **Option 3:** Manually update files to match templates\n\n";
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/auto_detect_platform.php * PATH: /wrappers/auto_detect_platform.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/auto_detect_platform.php * BRIEF: PHP wrapper for validate/auto_detect_platform.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'auto_detect_platform'; const SCRIPT_NAME = 'auto_detect_platform';
const SCRIPT_PATH = 'api/validate/auto_detect_platform.php'; const SCRIPT_PATH = 'validate/auto_detect_platform.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/bulk_sync.php * PATH: /wrappers/bulk_sync.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/automation/bulk_sync.php * BRIEF: PHP wrapper for automation/bulk_sync.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'bulk_sync'; const SCRIPT_NAME = 'bulk_sync';
const SCRIPT_PATH = 'api/automation/bulk_sync.php'; const SCRIPT_PATH = 'automation/bulk_sync.php';
const SCRIPT_CATEGORY = 'automation'; const SCRIPT_CATEGORY = 'automation';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_changelog.php * PATH: /wrappers/check_changelog.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_changelog.php * BRIEF: PHP wrapper for validate/check_changelog.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_changelog'; const SCRIPT_NAME = 'check_changelog';
const SCRIPT_PATH = 'api/validate/check_changelog.php'; const SCRIPT_PATH = 'validate/check_changelog.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_dolibarr_module.php * PATH: /wrappers/check_dolibarr_module.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_dolibarr_module.php * BRIEF: PHP wrapper for validate/check_dolibarr_module.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_dolibarr_module'; const SCRIPT_NAME = 'check_dolibarr_module';
const SCRIPT_PATH = 'api/validate/check_dolibarr_module.php'; const SCRIPT_PATH = 'validate/check_dolibarr_module.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_enterprise_readiness.php * PATH: /wrappers/check_enterprise_readiness.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_enterprise_readiness.php * BRIEF: PHP wrapper for validate/check_enterprise_readiness.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_enterprise_readiness'; const SCRIPT_NAME = 'check_enterprise_readiness';
const SCRIPT_PATH = 'api/validate/check_enterprise_readiness.php'; const SCRIPT_PATH = 'validate/check_enterprise_readiness.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_joomla_manifest.php * PATH: /wrappers/check_joomla_manifest.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_joomla_manifest.php * BRIEF: PHP wrapper for validate/check_joomla_manifest.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_joomla_manifest'; const SCRIPT_NAME = 'check_joomla_manifest';
const SCRIPT_PATH = 'api/validate/check_joomla_manifest.php'; const SCRIPT_PATH = 'validate/check_joomla_manifest.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_language_structure.php * PATH: /wrappers/check_language_structure.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_language_structure.php * BRIEF: PHP wrapper for validate/check_language_structure.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_language_structure'; const SCRIPT_NAME = 'check_language_structure';
const SCRIPT_PATH = 'api/validate/check_language_structure.php'; const SCRIPT_PATH = 'validate/check_language_structure.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_license_headers.php * PATH: /wrappers/check_license_headers.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_license_headers.php * BRIEF: PHP wrapper for validate/check_license_headers.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_license_headers'; const SCRIPT_NAME = 'check_license_headers';
const SCRIPT_PATH = 'api/validate/check_license_headers.php'; const SCRIPT_PATH = 'validate/check_license_headers.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_no_secrets.php * PATH: /wrappers/check_no_secrets.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_no_secrets.php * BRIEF: PHP wrapper for validate/check_no_secrets.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_no_secrets'; const SCRIPT_NAME = 'check_no_secrets';
const SCRIPT_PATH = 'api/validate/check_no_secrets.php'; const SCRIPT_PATH = 'validate/check_no_secrets.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_paths.php * PATH: /wrappers/check_paths.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_paths.php * BRIEF: PHP wrapper for validate/check_paths.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_paths'; const SCRIPT_NAME = 'check_paths';
const SCRIPT_PATH = 'api/validate/check_paths.php'; const SCRIPT_PATH = 'validate/check_paths.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_php_syntax.php * PATH: /wrappers/check_php_syntax.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_php_syntax.php * BRIEF: PHP wrapper for validate/check_php_syntax.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_php_syntax'; const SCRIPT_NAME = 'check_php_syntax';
const SCRIPT_PATH = 'api/validate/check_php_syntax.php'; const SCRIPT_PATH = 'validate/check_php_syntax.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_repo_health.php * PATH: /wrappers/check_repo_health.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_repo_health.php * BRIEF: PHP wrapper for validate/check_repo_health.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_repo_health'; const SCRIPT_NAME = 'check_repo_health';
const SCRIPT_PATH = 'api/validate/check_repo_health.php'; const SCRIPT_PATH = 'validate/check_repo_health.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_structure.php * PATH: /wrappers/check_structure.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_structure.php * BRIEF: PHP wrapper for validate/check_structure.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_structure'; const SCRIPT_NAME = 'check_structure';
const SCRIPT_PATH = 'api/validate/check_structure.php'; const SCRIPT_PATH = 'validate/check_structure.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_tabs.php * PATH: /wrappers/check_tabs.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_tabs.php * BRIEF: PHP wrapper for validate/check_tabs.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_tabs'; const SCRIPT_NAME = 'check_tabs';
const SCRIPT_PATH = 'api/validate/check_tabs.php'; const SCRIPT_PATH = 'validate/check_tabs.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_version_consistency.php * PATH: /wrappers/check_version_consistency.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_version_consistency.php * BRIEF: PHP wrapper for validate/check_version_consistency.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_version_consistency'; const SCRIPT_NAME = 'check_version_consistency';
const SCRIPT_PATH = 'api/validate/check_version_consistency.php'; const SCRIPT_PATH = 'validate/check_version_consistency.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/check_xml_wellformed.php * PATH: /wrappers/check_xml_wellformed.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/check_xml_wellformed.php * BRIEF: PHP wrapper for validate/check_xml_wellformed.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'check_xml_wellformed'; const SCRIPT_NAME = 'check_xml_wellformed';
const SCRIPT_PATH = 'api/validate/check_xml_wellformed.php'; const SCRIPT_PATH = 'validate/check_xml_wellformed.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/deploy_sftp.php * PATH: /wrappers/deploy_sftp.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/deploy/deploy-sftp.php * BRIEF: PHP wrapper for deploy/deploy-sftp.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'deploy_sftp'; const SCRIPT_NAME = 'deploy_sftp';
const SCRIPT_PATH = 'api/deploy/deploy-sftp.php'; const SCRIPT_PATH = 'deploy/deploy-sftp.php';
const SCRIPT_CATEGORY = 'deploy'; const SCRIPT_CATEGORY = 'deploy';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/fix_line_endings.php * PATH: /wrappers/fix_line_endings.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/fix/fix_line_endings.php * BRIEF: PHP wrapper for fix/fix_line_endings.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'fix_line_endings'; const SCRIPT_NAME = 'fix_line_endings';
const SCRIPT_PATH = 'api/fix/fix_line_endings.php'; const SCRIPT_PATH = 'fix/fix_line_endings.php';
const SCRIPT_CATEGORY = 'fix'; const SCRIPT_CATEGORY = 'fix';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/fix_permissions.php * PATH: /wrappers/fix_permissions.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/fix/fix_permissions.php * BRIEF: PHP wrapper for fix/fix_permissions.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'fix_permissions'; const SCRIPT_NAME = 'fix_permissions';
const SCRIPT_PATH = 'api/fix/fix_permissions.php'; const SCRIPT_PATH = 'fix/fix_permissions.php';
const SCRIPT_CATEGORY = 'fix'; const SCRIPT_CATEGORY = 'fix';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/fix_tabs.php * PATH: /wrappers/fix_tabs.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/fix/fix_tabs.php * BRIEF: PHP wrapper for fix/fix_tabs.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'fix_tabs'; const SCRIPT_NAME = 'fix_tabs';
const SCRIPT_PATH = 'api/fix/fix_tabs.php'; const SCRIPT_PATH = 'fix/fix_tabs.php';
const SCRIPT_CATEGORY = 'fix'; const SCRIPT_CATEGORY = 'fix';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/fix_trailing_spaces.php * PATH: /wrappers/fix_trailing_spaces.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/fix/fix_trailing_spaces.php * BRIEF: PHP wrapper for fix/fix_trailing_spaces.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'fix_trailing_spaces'; const SCRIPT_NAME = 'fix_trailing_spaces';
const SCRIPT_PATH = 'api/fix/fix_trailing_spaces.php'; const SCRIPT_PATH = 'fix/fix_trailing_spaces.php';
const SCRIPT_CATEGORY = 'fix'; const SCRIPT_CATEGORY = 'fix';
/** /**
+33 -33
View File
@@ -12,7 +12,7 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/gen_wrappers.php * PATH: /wrappers/gen_wrappers.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: Generate PHP CLI wrapper scripts for every PHP script in api/ * BRIEF: Generate PHP CLI wrapper scripts for every PHP script in the repo
*/ */
declare(strict_types=1); declare(strict_types=1);
@@ -24,43 +24,43 @@ declare(strict_types=1);
*/ */
const SCRIPTS = [ const SCRIPTS = [
// validate // validate
'auto_detect_platform' => ['api/validate/auto_detect_platform.php', 'validate'], 'auto_detect_platform' => ['validate/auto_detect_platform.php', 'validate'],
'check_changelog' => ['api/validate/check_changelog.php', 'validate'], 'check_changelog' => ['validate/check_changelog.php', 'validate'],
'check_dolibarr_module' => ['api/validate/check_dolibarr_module.php', 'validate'], 'check_dolibarr_module' => ['validate/check_dolibarr_module.php', 'validate'],
'check_enterprise_readiness' => ['api/validate/check_enterprise_readiness.php', 'validate'], 'check_enterprise_readiness' => ['validate/check_enterprise_readiness.php', 'validate'],
'check_joomla_manifest' => ['api/validate/check_joomla_manifest.php', 'validate'], 'check_joomla_manifest' => ['validate/check_joomla_manifest.php', 'validate'],
'check_language_structure' => ['api/validate/check_language_structure.php', 'validate'], 'check_language_structure' => ['validate/check_language_structure.php', 'validate'],
'check_license_headers' => ['api/validate/check_license_headers.php', 'validate'], 'check_license_headers' => ['validate/check_license_headers.php', 'validate'],
'check_no_secrets' => ['api/validate/check_no_secrets.php', 'validate'], 'check_no_secrets' => ['validate/check_no_secrets.php', 'validate'],
'check_paths' => ['api/validate/check_paths.php', 'validate'], 'check_paths' => ['validate/check_paths.php', 'validate'],
'check_php_syntax' => ['api/validate/check_php_syntax.php', 'validate'], 'check_php_syntax' => ['validate/check_php_syntax.php', 'validate'],
'check_repo_health' => ['api/validate/check_repo_health.php', 'validate'], 'check_repo_health' => ['validate/check_repo_health.php', 'validate'],
'check_structure' => ['api/validate/check_structure.php', 'validate'], 'check_structure' => ['validate/check_structure.php', 'validate'],
'check_tabs' => ['api/validate/check_tabs.php', 'validate'], 'check_tabs' => ['validate/check_tabs.php', 'validate'],
'check_version_consistency' => ['api/validate/check_version_consistency.php', 'validate'], 'check_version_consistency' => ['validate/check_version_consistency.php', 'validate'],
'check_xml_wellformed' => ['api/validate/check_xml_wellformed.php', 'validate'], 'check_xml_wellformed' => ['validate/check_xml_wellformed.php', 'validate'],
'scan_drift' => ['api/validate/scan_drift.php', 'validate'], 'scan_drift' => ['validate/scan_drift.php', 'validate'],
// automation // automation
'bulk_sync' => ['api/automation/bulk_sync.php', 'automation'], 'bulk_sync' => ['automation/bulk_sync.php', 'automation'],
// deploy // deploy
'deploy_sftp' => ['api/deploy/deploy-sftp.php', 'deploy'], 'deploy_sftp' => ['deploy/deploy-sftp.php', 'deploy'],
// fix // fix
'fix_line_endings' => ['api/fix/fix_line_endings.php', 'fix'], 'fix_line_endings' => ['fix/fix_line_endings.php', 'fix'],
'fix_permissions' => ['api/fix/fix_permissions.php', 'fix'], 'fix_permissions' => ['fix/fix_permissions.php', 'fix'],
'fix_tabs' => ['api/fix/fix_tabs.php', 'fix'], 'fix_tabs' => ['fix/fix_tabs.php', 'fix'],
'fix_trailing_spaces' => ['api/fix/fix_trailing_spaces.php', 'fix'], 'fix_trailing_spaces' => ['fix/fix_trailing_spaces.php', 'fix'],
// maintenance // maintenance
'pin_action_shas' => ['api/maintenance/pin_action_shas.php', 'maintenance'], 'pin_action_shas' => ['maintenance/pin_action_shas.php', 'maintenance'],
'setup_labels' => ['api/maintenance/setup_labels.php', 'maintenance'], 'setup_labels' => ['maintenance/setup_labels.php', 'maintenance'],
'sync_dolibarr_readmes' => ['api/maintenance/sync_dolibarr_readmes.php', 'maintenance'], 'sync_dolibarr_readmes' => ['maintenance/sync_dolibarr_readmes.php', 'maintenance'],
'update_sha_hashes' => ['api/maintenance/update_sha_hashes.php', 'maintenance'], 'update_sha_hashes' => ['maintenance/update_sha_hashes.php', 'maintenance'],
'update_version_from_readme' => ['api/maintenance/update_version_from_readme.php', 'maintenance'], 'update_version_from_readme' => ['maintenance/update_version_from_readme.php', 'maintenance'],
// plugin // plugin
'plugin_health_check' => ['api/plugin_health_check.php', 'plugin'], 'plugin_health_check' => ['plugin_health_check.php', 'plugin'],
'plugin_list' => ['api/plugin_list.php', 'plugin'], 'plugin_list' => ['plugin_list.php', 'plugin'],
'plugin_metrics' => ['api/plugin_metrics.php', 'plugin'], 'plugin_metrics' => ['plugin_metrics.php', 'plugin'],
'plugin_readiness' => ['api/plugin_readiness.php', 'plugin'], 'plugin_readiness' => ['plugin_readiness.php', 'plugin'],
'plugin_validate' => ['api/plugin_validate.php', 'plugin'], 'plugin_validate' => ['plugin_validate.php', 'plugin'],
]; ];
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/pin_action_shas.php * PATH: /wrappers/pin_action_shas.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/maintenance/pin_action_shas.php * BRIEF: PHP wrapper for maintenance/pin_action_shas.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'pin_action_shas'; const SCRIPT_NAME = 'pin_action_shas';
const SCRIPT_PATH = 'api/maintenance/pin_action_shas.php'; const SCRIPT_PATH = 'maintenance/pin_action_shas.php';
const SCRIPT_CATEGORY = 'maintenance'; const SCRIPT_CATEGORY = 'maintenance';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/plugin_health_check.php * PATH: /wrappers/plugin_health_check.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/plugin_health_check.php * BRIEF: PHP wrapper for plugin_health_check.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'plugin_health_check'; const SCRIPT_NAME = 'plugin_health_check';
const SCRIPT_PATH = 'api/plugin_health_check.php'; const SCRIPT_PATH = 'plugin_health_check.php';
const SCRIPT_CATEGORY = 'plugin'; const SCRIPT_CATEGORY = 'plugin';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/plugin_list.php * PATH: /wrappers/plugin_list.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/plugin_list.php * BRIEF: PHP wrapper for plugin_list.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'plugin_list'; const SCRIPT_NAME = 'plugin_list';
const SCRIPT_PATH = 'api/plugin_list.php'; const SCRIPT_PATH = 'plugin_list.php';
const SCRIPT_CATEGORY = 'plugin'; const SCRIPT_CATEGORY = 'plugin';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/plugin_metrics.php * PATH: /wrappers/plugin_metrics.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/plugin_metrics.php * BRIEF: PHP wrapper for plugin_metrics.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'plugin_metrics'; const SCRIPT_NAME = 'plugin_metrics';
const SCRIPT_PATH = 'api/plugin_metrics.php'; const SCRIPT_PATH = 'plugin_metrics.php';
const SCRIPT_CATEGORY = 'plugin'; const SCRIPT_CATEGORY = 'plugin';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/plugin_readiness.php * PATH: /wrappers/plugin_readiness.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/plugin_readiness.php * BRIEF: PHP wrapper for plugin_readiness.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'plugin_readiness'; const SCRIPT_NAME = 'plugin_readiness';
const SCRIPT_PATH = 'api/plugin_readiness.php'; const SCRIPT_PATH = 'plugin_readiness.php';
const SCRIPT_CATEGORY = 'plugin'; const SCRIPT_CATEGORY = 'plugin';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/plugin_validate.php * PATH: /wrappers/plugin_validate.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/plugin_validate.php * BRIEF: PHP wrapper for plugin_validate.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'plugin_validate'; const SCRIPT_NAME = 'plugin_validate';
const SCRIPT_PATH = 'api/plugin_validate.php'; const SCRIPT_PATH = 'plugin_validate.php';
const SCRIPT_CATEGORY = 'plugin'; const SCRIPT_CATEGORY = 'plugin';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/scan_drift.php * PATH: /wrappers/scan_drift.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/validate/scan_drift.php * BRIEF: PHP wrapper for validate/scan_drift.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'scan_drift'; const SCRIPT_NAME = 'scan_drift';
const SCRIPT_PATH = 'api/validate/scan_drift.php'; const SCRIPT_PATH = 'validate/scan_drift.php';
const SCRIPT_CATEGORY = 'validate'; const SCRIPT_CATEGORY = 'validate';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/setup_labels.php * PATH: /wrappers/setup_labels.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/maintenance/setup_labels.php * BRIEF: PHP wrapper for maintenance/setup_labels.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'setup_labels'; const SCRIPT_NAME = 'setup_labels';
const SCRIPT_PATH = 'api/maintenance/setup_labels.php'; const SCRIPT_PATH = 'maintenance/setup_labels.php';
const SCRIPT_CATEGORY = 'maintenance'; const SCRIPT_CATEGORY = 'maintenance';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/sync_dolibarr_readmes.php * PATH: /wrappers/sync_dolibarr_readmes.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/maintenance/sync_dolibarr_readmes.php * BRIEF: PHP wrapper for maintenance/sync_dolibarr_readmes.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'sync_dolibarr_readmes'; const SCRIPT_NAME = 'sync_dolibarr_readmes';
const SCRIPT_PATH = 'api/maintenance/sync_dolibarr_readmes.php'; const SCRIPT_PATH = 'maintenance/sync_dolibarr_readmes.php';
const SCRIPT_CATEGORY = 'maintenance'; const SCRIPT_CATEGORY = 'maintenance';
/** /**
+2 -2
View File
@@ -12,13 +12,13 @@
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
* PATH: /wrappers/update_sha_hashes.php * PATH: /wrappers/update_sha_hashes.php
* VERSION: 04.06.00 * VERSION: 04.06.00
* BRIEF: PHP wrapper for api/maintenance/update_sha_hashes.php * BRIEF: PHP wrapper for maintenance/update_sha_hashes.php
*/ */
declare(strict_types=1); declare(strict_types=1);
const SCRIPT_NAME = 'update_sha_hashes'; const SCRIPT_NAME = 'update_sha_hashes';
const SCRIPT_PATH = 'api/maintenance/update_sha_hashes.php'; const SCRIPT_PATH = 'maintenance/update_sha_hashes.php';
const SCRIPT_CATEGORY = 'maintenance'; const SCRIPT_CATEGORY = 'maintenance';
/** /**

Some files were not shown because too many files have changed in this diff Show More