a7df4d49b9
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 50s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Authored-by: Moko Consulting
283 lines
10 KiB
PHP
283 lines
10 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: 01.00.00
|
|
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
final class WikiSync
|
|
{
|
|
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 $dryRun = false;
|
|
private bool $allTemplates = false;
|
|
|
|
private int $synced = 0;
|
|
private int $created = 0;
|
|
private int $skipped = 0;
|
|
private int $errors = 0;
|
|
|
|
public function run(): int
|
|
{
|
|
$this->parseArgs();
|
|
|
|
if ($this->token === '') {
|
|
$this->log('ERROR: --token is required.');
|
|
$this->printUsage();
|
|
return 1;
|
|
}
|
|
|
|
if (empty($this->pages) && !$this->allTemplates) {
|
|
$this->log('ERROR: --page or --all-standards is required.');
|
|
$this->printUsage();
|
|
return 1;
|
|
}
|
|
|
|
// Discover template repos if --all-templates
|
|
if ($this->allTemplates || empty($this->targetRepos)) {
|
|
$this->targetRepos = $this->discoverTemplateRepos();
|
|
}
|
|
|
|
if (empty($this->targetRepos)) {
|
|
$this->log('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("Syncing " . count($this->pages) . " page(s) to " . count($this->targetRepos) . " repo(s)");
|
|
if ($this->dryRun) {
|
|
$this->log("[DRY RUN] No changes will be made.\n");
|
|
}
|
|
|
|
foreach ($this->pages as $pageName) {
|
|
$this->log("\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(" {$repo}: IDENTICAL (skipped)");
|
|
$this->skipped++;
|
|
continue;
|
|
}
|
|
|
|
if ($this->dryRun) {
|
|
$action = $existing !== null ? 'WOULD UPDATE' : 'WOULD CREATE';
|
|
$this->log(" {$repo}: {$action}");
|
|
continue;
|
|
}
|
|
|
|
if ($existing !== null) {
|
|
$ok = $this->updateWikiPage($repo, $pageName, $sourceContent);
|
|
$this->log(" {$repo}: " . ($ok ? 'UPDATED' : 'ERROR'));
|
|
$ok ? $this->synced++ : $this->errors++;
|
|
} else {
|
|
$ok = $this->createWikiPage($repo, $pageName, $sourceContent);
|
|
$this->log(" {$repo}: " . ($ok ? 'CREATED' : 'ERROR'));
|
|
$ok ? $this->created++ : $this->errors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->log("\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("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("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;
|
|
}
|
|
|
|
private function parseArgs(): void
|
|
{
|
|
global $argv;
|
|
$args = $argv;
|
|
for ($i = 1; $i < count($args); $i++) {
|
|
switch ($args[$i]) {
|
|
case '--token':
|
|
$this->token = $args[++$i] ?? '';
|
|
break;
|
|
case '--org':
|
|
$this->org = $args[++$i] ?? '';
|
|
break;
|
|
case '--source':
|
|
$this->sourceRepo = $args[++$i] ?? '';
|
|
break;
|
|
case '--target':
|
|
$this->targetRepos[] = $args[++$i] ?? '';
|
|
break;
|
|
case '--page':
|
|
$this->pages[] = $args[++$i] ?? '';
|
|
break;
|
|
case '--all-standards':
|
|
$this->pages = []; // will be populated from source wiki
|
|
$this->allTemplates = true;
|
|
break;
|
|
case '--all-templates':
|
|
$this->allTemplates = true;
|
|
break;
|
|
case '--dry-run':
|
|
$this->dryRun = true;
|
|
break;
|
|
case '--help':
|
|
case '-h':
|
|
$this->printUsage();
|
|
exit(0);
|
|
default:
|
|
$this->log("WARNING: Unknown argument: {$args[$i]}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function printUsage(): void
|
|
{
|
|
$this->log('Usage: wiki_sync.php --token <token> [options]');
|
|
$this->log('');
|
|
$this->log('Sync wiki pages from moko-platform to template repos.');
|
|
$this->log('');
|
|
$this->log('Options:');
|
|
$this->log(' --token <token> Gitea API token (required)');
|
|
$this->log(' --org <org> Organization (default: MokoConsulting)');
|
|
$this->log(' --source <repo> Source repo (default: moko-platform)');
|
|
$this->log(' --target <repo> Target repo (can repeat; default: all Template-* repos)');
|
|
$this->log(' --page <name> Page to sync (can repeat)');
|
|
$this->log(' --all-standards Sync all UPPERCASE standards pages');
|
|
$this->log(' --all-templates Target all Template-* repos');
|
|
$this->log(' --dry-run Show what would be done');
|
|
$this->log(' --help, -h Show this help');
|
|
$this->log('');
|
|
$this->log('Examples:');
|
|
$this->log(' php wiki_sync.php --token xxx --page MANIFEST_STANDARD --all-templates');
|
|
$this->log(' php wiki_sync.php --token xxx --all-standards --all-templates --dry-run');
|
|
$this->log(' php wiki_sync.php --token xxx --page WORKFLOW_STANDARDS --target Template-Joomla');
|
|
}
|
|
|
|
private function log(string $msg): void
|
|
{
|
|
fwrite(STDERR, $msg . "\n");
|
|
}
|
|
}
|
|
|
|
(new WikiSync())->run();
|