b3d9ee8255
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 36s
Wrap all CLI tools in cli/, automation/, maintenance/, deploy/, and release/ in classes extending CliFramework. Replaces manual $argv parsing with configure()/addArgument(), moves logic into run(): int, and converts fwrite(STDERR,...) to $this->log(). Two CLIApp subclasses (generate_dolibarr_version_txt, generate_joomla_update_xml) converted to extend CliFramework directly. Every script now gets free --help, --verbose, --quiet, --dry-run, --json, --no-color, banners, coloured logging, and progress bars. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
8.8 KiB
PHP
243 lines
8.8 KiB
PHP
#!/usr/bin/env php
|
|
<?php
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: moko-platform.CLI
|
|
* INGROUP: moko-platform
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /cli/wiki_sync.php
|
|
* VERSION: 09.21.07
|
|
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
|
|
|
use MokoEnterprise\CliFramework;
|
|
|
|
class WikiSyncCli extends CliFramework
|
|
{
|
|
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
|
private string $token = '';
|
|
private string $org = 'MokoConsulting';
|
|
private string $sourceRepo = 'moko-platform';
|
|
private array $targetRepos = [];
|
|
private array $pages = [];
|
|
private bool $allTemplates = false;
|
|
private bool $allStandards = false;
|
|
|
|
private int $synced = 0;
|
|
private int $created = 0;
|
|
private int $skipped = 0;
|
|
private int $errors = 0;
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Sync wiki pages from moko-platform to template repos');
|
|
$this->addArgument('--token', 'Gitea API token (required)', '');
|
|
$this->addArgument('--org', 'Organization (default: MokoConsulting)', 'MokoConsulting');
|
|
$this->addArgument('--source', 'Source repo (default: moko-platform)', 'moko-platform');
|
|
$this->addArgument('--target', 'Target repo (can repeat)', '');
|
|
$this->addArgument('--page', 'Page to sync (can repeat)', '');
|
|
$this->addArgument('--all-standards', 'Sync all UPPERCASE standards pages', false);
|
|
$this->addArgument('--all-templates', 'Target all Template-* repos', false);
|
|
}
|
|
|
|
protected function run(): int
|
|
{
|
|
$this->token = $this->getArgument('--token');
|
|
$this->org = $this->getArgument('--org');
|
|
$this->sourceRepo = $this->getArgument('--source');
|
|
$this->allStandards = (bool) $this->getArgument('--all-standards');
|
|
$this->allTemplates = (bool) $this->getArgument('--all-templates');
|
|
|
|
// Handle repeatable args from raw argv
|
|
global $argv;
|
|
foreach ($argv as $i => $arg) {
|
|
if ($arg === '--target' && isset($argv[$i + 1])) {
|
|
$this->targetRepos[] = $argv[$i + 1];
|
|
}
|
|
if ($arg === '--page' && isset($argv[$i + 1])) {
|
|
$this->pages[] = $argv[$i + 1];
|
|
}
|
|
}
|
|
|
|
if ($this->token === '') {
|
|
$this->log('ERROR', '--token is required.');
|
|
return 1;
|
|
}
|
|
|
|
if (empty($this->pages) && !$this->allStandards) {
|
|
$this->log('ERROR', '--page or --all-standards is required.');
|
|
return 1;
|
|
}
|
|
|
|
// Discover template repos if --all-templates
|
|
if ($this->allTemplates || empty($this->targetRepos)) {
|
|
$this->targetRepos = $this->discoverTemplateRepos();
|
|
}
|
|
|
|
if (empty($this->targetRepos)) {
|
|
$this->log('INFO', 'No target repos found.');
|
|
return 0;
|
|
}
|
|
|
|
// If --all-standards, get all pages that start with uppercase
|
|
if (empty($this->pages)) {
|
|
$this->pages = $this->getStandardsPages();
|
|
}
|
|
|
|
$this->log('INFO', "Syncing " . count($this->pages) . " page(s) to " . count($this->targetRepos) . " repo(s)");
|
|
if ($this->dryRun) {
|
|
$this->log('INFO', "[DRY RUN] No changes will be made.\n");
|
|
}
|
|
|
|
foreach ($this->pages as $pageName) {
|
|
$this->log('INFO', "\n--- Page: {$pageName} ---");
|
|
$sourceContent = $this->getWikiPage($this->sourceRepo, $pageName);
|
|
if ($sourceContent === null) {
|
|
$this->log('WARNING', "page not found in {$this->sourceRepo}");
|
|
$this->errors++;
|
|
continue;
|
|
}
|
|
|
|
foreach ($this->targetRepos as $repo) {
|
|
$existing = $this->getWikiPage($repo, $pageName);
|
|
if ($existing !== null && $existing === $sourceContent) {
|
|
$this->log('INFO', " {$repo}: IDENTICAL (skipped)");
|
|
$this->skipped++;
|
|
continue;
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
$action = $existing !== null ? 'WOULD UPDATE' : 'WOULD CREATE';
|
|
$this->log('INFO', " {$repo}: {$action}");
|
|
continue;
|
|
}
|
|
|
|
if ($existing !== null) {
|
|
$ok = $this->updateWikiPage($repo, $pageName, $sourceContent);
|
|
$this->log('INFO', " {$repo}: " . ($ok ? 'UPDATED' : 'ERROR'));
|
|
$ok ? $this->synced++ : $this->errors++;
|
|
} else {
|
|
$ok = $this->createWikiPage($repo, $pageName, $sourceContent);
|
|
$this->log('INFO', " {$repo}: " . ($ok ? 'CREATED' : 'ERROR'));
|
|
$ok ? $this->created++ : $this->errors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->log('INFO', "\nDone: {$this->synced} updated, {$this->created} created, {$this->skipped} skipped, {$this->errors} error(s)");
|
|
return $this->errors > 0 ? 1 : 0;
|
|
}
|
|
|
|
private function discoverTemplateRepos(): array
|
|
{
|
|
$repos = $this->apiGet("/orgs/{$this->org}/repos?limit=100");
|
|
$templates = [];
|
|
foreach ($repos as $repo) {
|
|
if (str_starts_with($repo['name'], 'Template-') && !($repo['archived'] ?? false)) {
|
|
$templates[] = $repo['name'];
|
|
}
|
|
}
|
|
sort($templates);
|
|
$this->log('INFO', "Found template repos: " . implode(', ', $templates));
|
|
return $templates;
|
|
}
|
|
|
|
private function getStandardsPages(): array
|
|
{
|
|
$pages = $this->apiGet("/repos/{$this->org}/{$this->sourceRepo}/wiki/pages");
|
|
$standards = [];
|
|
foreach ($pages as $page) {
|
|
$title = $page['title'] ?? '';
|
|
// Sync pages that are all-caps with underscores (standards pages)
|
|
if (preg_match('/^[A-Z][A-Z0-9_-]+$/', $title)) {
|
|
$standards[] = $title;
|
|
}
|
|
}
|
|
sort($standards);
|
|
$this->log('INFO', "Found " . count($standards) . " standards pages: " . implode(', ', $standards));
|
|
return $standards;
|
|
}
|
|
|
|
private function getWikiPage(string $repo, string $pageName): ?string
|
|
{
|
|
$data = $this->apiGet("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}");
|
|
if ($data === null || !isset($data['content_base64'])) {
|
|
return null;
|
|
}
|
|
return base64_decode($data['content_base64']);
|
|
}
|
|
|
|
private function createWikiPage(string $repo, string $pageName, string $content): bool
|
|
{
|
|
$payload = json_encode([
|
|
'title' => $pageName,
|
|
'content_base64' => base64_encode($content),
|
|
]);
|
|
return $this->apiPost("/repos/{$this->org}/{$repo}/wiki/new", $payload) !== null;
|
|
}
|
|
|
|
private function updateWikiPage(string $repo, string $pageName, string $content): bool
|
|
{
|
|
$payload = json_encode([
|
|
'title' => $pageName,
|
|
'content_base64' => base64_encode($content),
|
|
]);
|
|
return $this->apiPatch("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}", $payload) !== null;
|
|
}
|
|
|
|
private function apiGet(string $endpoint): ?array
|
|
{
|
|
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
|
$opts = [
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => "Authorization: token {$this->token}\r\nAccept: application/json\r\n",
|
|
'ignore_errors' => true,
|
|
],
|
|
];
|
|
$ctx = stream_context_create($opts);
|
|
$result = @file_get_contents($url, false, $ctx);
|
|
if ($result === false) return null;
|
|
$data = json_decode($result, true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
|
|
private function apiPost(string $endpoint, string $payload): ?array
|
|
{
|
|
return $this->apiWrite('POST', $endpoint, $payload);
|
|
}
|
|
|
|
private function apiPatch(string $endpoint, string $payload): ?array
|
|
{
|
|
return $this->apiWrite('PATCH', $endpoint, $payload);
|
|
}
|
|
|
|
private function apiWrite(string $method, string $endpoint, string $payload): ?array
|
|
{
|
|
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
|
|
$opts = [
|
|
'http' => [
|
|
'method' => $method,
|
|
'header' => "Authorization: token {$this->token}\r\nContent-Type: application/json\r\nAccept: application/json\r\n",
|
|
'content' => $payload,
|
|
'ignore_errors' => true,
|
|
],
|
|
];
|
|
$ctx = stream_context_create($opts);
|
|
$result = @file_get_contents($url, false, $ctx);
|
|
if ($result === false) return null;
|
|
$data = json_decode($result, true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
}
|
|
|
|
$app = new WikiSyncCli();
|
|
exit($app->execute());
|