Public Access
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c87125ab10 | |||
| 4e3e9b7899 | |||
| 5a918e2d1f | |||
| 2d856c8846 | |||
| 200956b91a | |||
| bb24a58903 | |||
| 8cea26d748 | |||
| eb557302ce | |||
| 7e555eacf0 | |||
| 1d897ba115 | |||
| 8e6aaaff88 | |||
| fef27eb5d1 | |||
| 6d8e09827d |
@@ -30,6 +30,15 @@ on:
|
||||
types: [opened, closed]
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '.mokogitea/workflows/**'
|
||||
- '*.md'
|
||||
- 'wiki/**'
|
||||
- '.editorconfig'
|
||||
- '.gitignore'
|
||||
- '.gitattributes'
|
||||
- '.gitmessage'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Enforce branch protection rules across all org repos.
|
||||
# Runs weekly and on manual dispatch.
|
||||
|
||||
name: "Org: Enforce Branch Protections"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Every Monday at 6am UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run (show changes without applying)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
enforce:
|
||||
name: Enforce Branch Protections
|
||||
runs-on: release
|
||||
|
||||
steps:
|
||||
- name: Checkout MokoCLI
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
if ! command -v php > /dev/null 2>&1; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-curl > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Run branch protection enforcement
|
||||
run: |
|
||||
DRY_RUN=""
|
||||
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
||||
DRY_RUN="--dry-run"
|
||||
fi
|
||||
php cli/branch_protect_org.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--org "MokoConsulting" \
|
||||
$DRY_RUN
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Branch Protection Enforcement" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All repos checked for main, dev, rc, beta, alpha protections" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Push whitelist: jmiller only" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 01.00.00
|
||||
# VERSION: 09.39.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/security-audit.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
||||
|
||||
name: "Universal: Security Audit"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Dependency Audit
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Composer audit
|
||||
if: hashFiles('composer.lock') != ''
|
||||
run: |
|
||||
echo "=== Composer Security Audit ==="
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
|
||||
fi
|
||||
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
|
||||
RESULT=$?
|
||||
if [ $RESULT -ne 0 ]; then
|
||||
echo "::warning::Composer vulnerabilities found"
|
||||
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "No known vulnerabilities in composer dependencies"
|
||||
fi
|
||||
|
||||
- name: NPM audit
|
||||
if: hashFiles('package-lock.json') != ''
|
||||
run: |
|
||||
echo "=== NPM Security Audit ==="
|
||||
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
|
||||
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
|
||||
echo "No known vulnerabilities in npm dependencies"
|
||||
else
|
||||
echo "::warning::NPM vulnerabilities found"
|
||||
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Notify on vulnerabilities
|
||||
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} has vulnerable dependencies" \
|
||||
-H "Tags: lock,warning" \
|
||||
-H "Priority: high" \
|
||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
+3
-3
@@ -12,12 +12,12 @@ BRIEF: Release changelog
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [09.39.00] --- 2026-06-23
|
||||
|
||||
## [09.38.00] --- 2026-06-21
|
||||
|
||||
## [09.38.00] --- 2026-06-21
|
||||
|
||||
## [09.38.00] --- 2026-06-21
|
||||
|
||||
## [09.37.00] --- 2026-06-21
|
||||
|
||||
## [09.37.00] --- 2026-06-21
|
||||
## [09.38.00] --- 2026-06-21
|
||||
|
||||
@@ -6,7 +6,7 @@ DEFGROUP: MokoPlatform.Root
|
||||
INGROUP: MokoPlatform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
PATH: /README.md
|
||||
VERSION: 09.38.00
|
||||
VERSION: 09.39.00
|
||||
BRIEF: Project overview and documentation
|
||||
-->
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoCLI
|
||||
* @subpackage cli
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Enforce branch protection rules across all repos in the org.
|
||||
*
|
||||
* Usage:
|
||||
* php cli/branch_protect_org.php --token TOKEN [--org MokoConsulting] [--dry-run]
|
||||
*
|
||||
* Branch flow: feature/* -> dev -> rc -> main
|
||||
* main, dev, rc: push whitelist only (no direct push)
|
||||
* alpha, beta: push whitelist only (pre-release)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$options = getopt('', ['token:', 'org:', 'api-base:', 'dry-run', 'help']);
|
||||
|
||||
if (isset($options['help']) || empty($options['token'])) {
|
||||
echo "Usage: php cli/branch_protect_org.php --token TOKEN [--org ORG] [--api-base URL] [--dry-run]\n";
|
||||
echo "\n";
|
||||
echo "Options:\n";
|
||||
echo " --token Gitea API token (required)\n";
|
||||
echo " --org Organization name (default: MokoConsulting)\n";
|
||||
echo " --api-base API base URL (default: https://git.mokoconsulting.tech/api/v1)\n";
|
||||
echo " --dry-run Show what would be changed without making changes\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$token = $options['token'];
|
||||
$org = $options['org'] ?? 'MokoConsulting';
|
||||
$apiBase = rtrim($options['api-base'] ?? 'https://git.mokoconsulting.tech/api/v1', '/');
|
||||
$dryRun = isset($options['dry-run']);
|
||||
|
||||
// Protected branches and their rules
|
||||
$branchRules = [
|
||||
// Primary branches (flow: feature/* -> dev -> rc -> main)
|
||||
'main' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'dev' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'rc' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'beta' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'alpha' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
|
||||
// Synonyms (prevent bypass via alternate names)
|
||||
'master' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'develop' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'release' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'production' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'stable' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
'staging' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
||||
];
|
||||
|
||||
function apiRequest(string $method, string $url, string $token, ?array $body = null): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: token ' . $token,
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
|
||||
if ($body !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return [
|
||||
'status' => $httpCode,
|
||||
'data' => json_decode($response, true) ?: [],
|
||||
];
|
||||
}
|
||||
|
||||
// 1. List all org repos
|
||||
echo "Fetching repos for {$org}...\n";
|
||||
$page = 1;
|
||||
$repos = [];
|
||||
|
||||
do {
|
||||
$result = apiRequest('GET', "{$apiBase}/orgs/{$org}/repos?limit=50&page={$page}", $token);
|
||||
$batch = $result['data'];
|
||||
$repos = array_merge($repos, $batch);
|
||||
$page++;
|
||||
} while (count($batch) === 50);
|
||||
|
||||
echo sprintf("Found %d repos\n\n", count($repos));
|
||||
|
||||
$summary = ['protected' => 0, 'added' => 0, 'skipped' => 0, 'errors' => 0];
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$repoName = $repo['name'];
|
||||
|
||||
if ($repo['archived'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get existing protections
|
||||
$existing = apiRequest('GET', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token);
|
||||
$existingNames = array_map(fn($p) => $p['branch_name'] ?? '', $existing['data'] ?: []);
|
||||
|
||||
$added = [];
|
||||
$skipped = [];
|
||||
|
||||
foreach ($branchRules as $branch => $rules) {
|
||||
if (in_array($branch, $existingNames, true)) {
|
||||
$skipped[] = $branch;
|
||||
$summary['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$added[] = $branch;
|
||||
$summary['added']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = array_merge($rules, ['branch_name' => $branch]);
|
||||
$result = apiRequest('POST', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token, $body);
|
||||
|
||||
if ($result['status'] >= 200 && $result['status'] < 300) {
|
||||
$added[] = $branch;
|
||||
$summary['added']++;
|
||||
} elseif ($result['status'] === 422) {
|
||||
$skipped[] = $branch;
|
||||
$summary['skipped']++;
|
||||
} else {
|
||||
$added[] = "{$branch}(ERR:{$result['status']})";
|
||||
$summary['errors']++;
|
||||
}
|
||||
}
|
||||
|
||||
$summary['protected']++;
|
||||
|
||||
if (!empty($added)) {
|
||||
$prefix = $dryRun ? '[DRY-RUN] ' : '';
|
||||
echo sprintf(" %s%-35s added: %s\n", $prefix, $repoName, implode(', ', $added));
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
echo sprintf("Summary: %d repos, %d rules added, %d already existed, %d errors\n",
|
||||
$summary['protected'], $summary['added'], $summary['skipped'], $summary['errors']);
|
||||
|
||||
if ($dryRun) {
|
||||
echo "\n(Dry run - no changes made)\n";
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/branch_rename.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_push.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_trigger.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Trigger a workflow across multiple repos at once
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_dashboard.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Generate unified client dashboard HTML
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_inventory.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_provision.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Provision a new client environment end-to-end
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/grafana_dashboard.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Manage Grafana dashboards via API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_build.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||
* NOTE: Called by pre-release and auto-release workflows.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_metadata_validate.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_detect.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_integrity.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_licensing.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_read.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Read repo metadata from Gitea manifest API, auto-detect the rest
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/platform_detect.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_cascade.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Cascade release zip to all lower stability channels
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_publish.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/scaffold_client.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/updates_xml_sync.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Sync updates.xml to target branches via Gitea API
|
||||
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
||||
* is modified on the current branch. Pushes the file to other branches
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_auto_bump.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||
*/
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ class VersionBumpCli extends CliFramework
|
||||
/**
|
||||
* Scan git release tags for the highest version across all channels.
|
||||
*
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.38.00)" in
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.39.00)" in
|
||||
* git tags (stable, release-candidate, development, etc.) to find the
|
||||
* highest version that has been released on any channel.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_check.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/wiki_sync.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Sync select wiki pages from mokocli to all template repos
|
||||
*/
|
||||
|
||||
|
||||
+128
-4
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/workflow_sync.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
||||
*/
|
||||
|
||||
@@ -42,9 +42,13 @@ class WorkflowSyncCli extends CliFramework
|
||||
'joomla' => ['deploy-manual.yml'],
|
||||
];
|
||||
|
||||
/** Prefix for custom workflows preserved during orphan cleanup. */
|
||||
private const CUSTOM_PREFIX = 'custom-';
|
||||
|
||||
private int $updated = 0;
|
||||
private int $created = 0;
|
||||
private int $skipped = 0;
|
||||
private int $deleted = 0;
|
||||
private int $errors = 0;
|
||||
|
||||
protected function configure(): void
|
||||
@@ -56,6 +60,7 @@ class WorkflowSyncCli extends CliFramework
|
||||
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
||||
$this->addArgument('--phase', 'Phase to run: all, templates, repos (default: all)', 'all');
|
||||
$this->addArgument('--platform-filter', 'Only sync repos matching this platform', '');
|
||||
$this->addArgument('--delete-orphans', 'Delete workflows not in template (preserves custom-* and custom/)', false);
|
||||
}
|
||||
|
||||
protected function run(): int
|
||||
@@ -114,7 +119,7 @@ class WorkflowSyncCli extends CliFramework
|
||||
|
||||
echo "\n";
|
||||
$this->log('INFO', "Done: {$this->created} created, {$this->updated} updated, "
|
||||
. "{$this->skipped} skipped, {$this->errors} error(s).");
|
||||
. "{$this->deleted} deleted, {$this->skipped} skipped, {$this->errors} error(s).");
|
||||
|
||||
return $this->errors > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -275,14 +280,15 @@ class WorkflowSyncCli extends CliFramework
|
||||
|
||||
foreach ($workflows as $workflow) {
|
||||
$filename = $workflow['name'];
|
||||
$destPath = '.mokogitea/workflows/' . $filename;
|
||||
$label = "{$repoFullName}/{$filename}";
|
||||
|
||||
// Skip platform-excluded workflows
|
||||
if (in_array($filename, self::PLATFORM_EXCLUDES[$platform] ?? [], true)) {
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'EXCLUDED (platform)');
|
||||
$this->skipped++;
|
||||
continue;
|
||||
}
|
||||
$destPath = '.mokogitea/workflows/' . $filename;
|
||||
$label = "{$repoFullName}/{$filename}";
|
||||
|
||||
// Get source content from template
|
||||
$sourceContent = $this->getFileContent(
|
||||
@@ -303,6 +309,14 @@ class WorkflowSyncCli extends CliFramework
|
||||
$destPath, $sourceContent, $branch, $commitMsg, $label
|
||||
);
|
||||
}
|
||||
|
||||
// Delete orphan workflows if enabled
|
||||
if ($this->getArgument('--delete-orphans', false)) {
|
||||
$templateNames = array_map(fn($w) => $w['name'], $workflows);
|
||||
$this->deleteOrphanWorkflows(
|
||||
$giteaUrl, $token, $org, $repoName, $branch, $templateNames, $platform
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
@@ -406,6 +420,116 @@ class WorkflowSyncCli extends CliFramework
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workflows in a repo that are NOT in the template and NOT custom.
|
||||
*
|
||||
* Protected from deletion:
|
||||
* - Files matching template workflow names
|
||||
* - Files with `custom-` prefix (convention for repo-specific workflows)
|
||||
* - Directories named `custom` (future: subfolder discovery)
|
||||
* - Platform-excluded workflows
|
||||
*/
|
||||
private function deleteOrphanWorkflows(
|
||||
string $giteaUrl,
|
||||
string $token,
|
||||
string $org,
|
||||
string $repoName,
|
||||
string $branch,
|
||||
array $templateNames,
|
||||
string $platform
|
||||
): void {
|
||||
$repoWorkflows = $this->listWorkflows($giteaUrl, $token, $org, $repoName, $branch);
|
||||
if ($repoWorkflows === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$platformExcludes = self::PLATFORM_EXCLUDES[$platform] ?? [];
|
||||
|
||||
foreach ($repoWorkflows as $workflow) {
|
||||
$name = $workflow['name'];
|
||||
|
||||
// Keep if it's in the template
|
||||
if (in_array($name, $templateNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep if it has the custom- prefix
|
||||
if (str_starts_with($name, self::CUSTOM_PREFIX)) {
|
||||
$label = "{$org}/{$repoName}/{$name}";
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'KEPT (custom)');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep if it's platform-excluded (legitimately skipped during sync)
|
||||
if (in_array($name, $platformExcludes, true)) {
|
||||
$label = "{$org}/{$repoName}/{$name}";
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'KEPT (platform-excluded)');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete orphan
|
||||
$filePath = '.mokogitea/workflows/' . $name;
|
||||
$label = "{$org}/{$repoName}/{$name}";
|
||||
|
||||
if ($this->dryRun) {
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'WOULD DELETE');
|
||||
$this->deleted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$deleted = $this->deleteFile($giteaUrl, $token, $org, $repoName, $filePath, $branch);
|
||||
if ($deleted) {
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'DELETED');
|
||||
$this->deleted++;
|
||||
} else {
|
||||
fprintf(STDERR, "%-45s | %s\n", $label, 'ERROR (delete)');
|
||||
$this->errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from a repo via the Gitea Contents API.
|
||||
*/
|
||||
private function deleteFile(
|
||||
string $giteaUrl,
|
||||
string $token,
|
||||
string $org,
|
||||
string $repoName,
|
||||
string $filePath,
|
||||
string $branch
|
||||
): bool {
|
||||
// Get SHA first
|
||||
$existing = $this->apiRequest(
|
||||
$giteaUrl, $token, 'GET',
|
||||
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}"
|
||||
);
|
||||
|
||||
if ($existing['code'] !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($existing['body'], true);
|
||||
$sha = $data['sha'] ?? '';
|
||||
if ($sha === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'sha' => $sha,
|
||||
'message' => "chore: delete orphan workflow {$filePath} [skip ci]",
|
||||
'branch' => $branch,
|
||||
]);
|
||||
|
||||
$response = $this->apiRequest(
|
||||
$giteaUrl, $token, 'DELETE',
|
||||
"/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}",
|
||||
$payload
|
||||
);
|
||||
|
||||
return $response['code'] === 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* List workflow files in a repo's .mokogitea/workflows/ directory.
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/backup-before-deploy.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/deploy-dolibarr.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/health-check.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/rollback-joomla.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/sync-joomla.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Sync Joomla site directories between two servers via rsync over SSH
|
||||
*/
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
VERSION: 09.38.00
|
||||
VERSION: 09.39.00
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -10,7 +10,7 @@ DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.38.00
|
||||
VERSION: 09.39.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
DEFGROUP:
|
||||
INGROUP: Project.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli-Template-Generic
|
||||
VERSION: 09.38.00
|
||||
VERSION: 09.39.00
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
||||
INGROUP: [PROJECT_NAME].Documentation
|
||||
REPO: [REPOSITORY_URL]
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.38.00
|
||||
VERSION: 09.39.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class VersionBumpTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.38.00 -->\nSome content\n"
|
||||
"<!-- VERSION: 09.39.00 -->\nSome content\n"
|
||||
);
|
||||
|
||||
$this->execute();
|
||||
|
||||
@@ -34,7 +34,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"# Test\n<!-- VERSION: 09.38.00 -->\n"
|
||||
"# Test\n<!-- VERSION: 09.39.00 -->\n"
|
||||
);
|
||||
|
||||
$this->assertSame('02.03.04', trim($this->runScript()));
|
||||
@@ -68,7 +68,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.38.00 -->\n"
|
||||
"<!-- VERSION: 09.39.00 -->\n"
|
||||
);
|
||||
mkdir("{$this->tmpDir}/src", 0755, true);
|
||||
file_put_contents(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /validate/check_file_integrity.php
|
||||
* VERSION: 09.38.00
|
||||
* VERSION: 09.39.00
|
||||
* BRIEF: Compare deployed files on a remote server against the local repository to detect drift
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user