ca55e5d2d2
Introduces SourceResolver utility class with source/ → src/ → htdocs/ fallback chain, replacing hardcoded src/ references across 28 files. This enables renaming root-level src/ to source/ in all repos while maintaining backwards compatibility during the transition. Phase 1: New lib/Enterprise/SourceResolver.php with resolve(), resolveAbsolute(), globSource(), findUnderSource(), warnIfLegacy() Phase 2: Updated 19 CLI/deploy tools to use SourceResolver Phase 3: Updated 7 validator/lib files (McpServerPlugin, PackageBuilder, RepositorySynchronizer, auto_detect_platform, check_dolibarr_module, check_client_theme, check_structure) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
281 lines
11 KiB
PHP
281 lines
11 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/manifest_licensing.php
|
|
* VERSION: 01.00.00
|
|
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
|
|
|
use MokoEnterprise\{CliFramework, SourceResolver};
|
|
|
|
/**
|
|
* Reads the <licensing> block from .mokogitea/manifest.xml and ensures that the
|
|
* Joomla extension manifest contains the correct <updateservers> and <dlid> tags.
|
|
*
|
|
* manifest.xml licensing block example:
|
|
*
|
|
* <licensing>
|
|
* <enabled>true</enabled>
|
|
* <dlid>true</dlid>
|
|
* <update-server>https://git.mokoconsulting.tech/{org}/{repo}/updates.xml</update-server>
|
|
* <update-server-name>MyExtension Updates</update-server-name>
|
|
* </licensing>
|
|
*
|
|
* Supports {org} and {repo} placeholders in update-server URL, resolved from
|
|
* the manifest's <identity> block or git remote.
|
|
*/
|
|
class ManifestLicensingCli extends CliFramework
|
|
{
|
|
protected function configure(): void
|
|
{
|
|
$this->setDescription('Ensure licensing tags (updateservers, dlid) in Joomla extension manifests');
|
|
$this->addArgument('--path', 'Repository root path', '.');
|
|
$this->addArgument('--fix', 'Apply fixes (default: dry-run check only)', false);
|
|
$this->addArgument('--github-output', 'Write results to $GITHUB_OUTPUT', false);
|
|
}
|
|
|
|
protected function run(): int
|
|
{
|
|
$root = realpath($this->getArgument('--path')) ?: $this->getArgument('--path');
|
|
$fix = (bool) $this->getArgument('--fix');
|
|
$ghOutput = (bool) $this->getArgument('--github-output');
|
|
|
|
// ── 1. Read manifest.xml ──────────────────────────────────────────
|
|
$manifestFile = "{$root}/.mokogitea/manifest.xml";
|
|
|
|
if (!file_exists($manifestFile)) {
|
|
$this->log('WARN', "No manifest.xml found at {$manifestFile}");
|
|
$this->outputResult($ghOutput, 'skipped', 'No manifest.xml');
|
|
return 0;
|
|
}
|
|
|
|
$xml = @simplexml_load_file($manifestFile);
|
|
|
|
if ($xml === false) {
|
|
$this->log('ERROR', "Failed to parse {$manifestFile}");
|
|
return 1;
|
|
}
|
|
|
|
// ── 2. Check if licensing is enabled ──────────────────────────────
|
|
if (!isset($xml->licensing) || (string) ($xml->licensing->enabled ?? '') !== 'true') {
|
|
$this->log('INFO', 'Licensing not enabled in manifest.xml — skipping');
|
|
$this->outputResult($ghOutput, 'skipped', 'Licensing not enabled');
|
|
return 0;
|
|
}
|
|
|
|
$licensingNode = $xml->licensing;
|
|
$dlidEnabled = ((string) ($licensingNode->dlid ?? 'true')) === 'true';
|
|
$updateServerUrl = (string) ($licensingNode->{'update-server'} ?? '');
|
|
$updateServerName = (string) ($licensingNode->{'update-server-name'} ?? '');
|
|
|
|
// ── 3. Resolve placeholders ───────────────────────────────────────
|
|
$org = (string) ($xml->identity->org ?? '');
|
|
$repo = (string) ($xml->identity->name ?? '');
|
|
|
|
// Fallback to git remote if manifest doesn't have org/name
|
|
if (empty($org) || empty($repo)) {
|
|
$remote = trim((string) @shell_exec("cd " . escapeshellarg($root) . " && git remote get-url origin 2>/dev/null"));
|
|
|
|
if (preg_match('#[/:]([^/]+)/([^/.]+?)(?:\.git)?$#', $remote, $m)) {
|
|
if (empty($org)) {
|
|
$org = $m[1];
|
|
}
|
|
if (empty($repo)) {
|
|
$repo = $m[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default update server URL if not specified
|
|
if (empty($updateServerUrl) && !empty($org) && !empty($repo)) {
|
|
$updateServerUrl = "https://git.mokoconsulting.tech/{$org}/{$repo}/updates.xml";
|
|
}
|
|
|
|
// Resolve {org} and {repo} placeholders
|
|
$updateServerUrl = str_replace(['{org}', '{repo}'], [$org, $repo], $updateServerUrl);
|
|
|
|
// Default server name from display-name or repo name
|
|
if (empty($updateServerName)) {
|
|
$displayName = (string) ($xml->identity->{'display-name'} ?? $repo);
|
|
$updateServerName = $displayName . ' Updates';
|
|
}
|
|
|
|
if (empty($updateServerUrl)) {
|
|
$this->log('ERROR', 'Cannot determine update server URL — set <update-server> in manifest.xml or ensure org/repo are available');
|
|
return 1;
|
|
}
|
|
|
|
$this->log('INFO', "Licensing enabled — org={$org}, repo={$repo}");
|
|
$this->log('INFO', "Update server: {$updateServerUrl}");
|
|
$this->log('INFO', "DLID required: " . ($dlidEnabled ? 'yes' : 'no'));
|
|
|
|
// ── 4. Find Joomla extension manifests ────────────────────────────
|
|
$xmlFiles = array_merge(
|
|
SourceResolver::globSource($root, '*.xml'),
|
|
SourceResolver::globSource($root, 'packages/*/*.xml'),
|
|
glob("{$root}/*.xml") ?: []
|
|
);
|
|
|
|
$packageManifest = null;
|
|
|
|
foreach ($xmlFiles as $file) {
|
|
$content = file_get_contents($file);
|
|
|
|
if (!str_contains($content, '<extension')) {
|
|
continue;
|
|
}
|
|
|
|
// Find the package manifest (type="package") or the main extension manifest
|
|
if (str_contains($content, 'type="package"')) {
|
|
$packageManifest = $file;
|
|
break;
|
|
}
|
|
|
|
// Fallback: first extension manifest found
|
|
if ($packageManifest === null) {
|
|
$packageManifest = $file;
|
|
}
|
|
}
|
|
|
|
if ($packageManifest === null) {
|
|
$this->log('WARN', 'No Joomla extension manifest found');
|
|
$this->outputResult($ghOutput, 'skipped', 'No extension manifest');
|
|
return 0;
|
|
}
|
|
|
|
$relPath = str_replace($root . '/', '', str_replace('\\', '/', $packageManifest));
|
|
$this->log('INFO', "Package manifest: {$relPath}");
|
|
|
|
// ── 5. Check and fix the manifest ─────────────────────────────────
|
|
$content = file_get_contents($packageManifest);
|
|
$original = $content;
|
|
$changes = [];
|
|
|
|
// --- 5a. Ensure <updateservers> block with correct URL ---
|
|
if (preg_match('#<updateservers>\s*</updateservers>#s', $content)) {
|
|
// Empty updateservers block — inject the server
|
|
$replacement = "<updateservers>\n"
|
|
. " <server type=\"extension\" name=\"{$updateServerName}\">{$updateServerUrl}</server>\n"
|
|
. " </updateservers>";
|
|
$content = preg_replace('#<updateservers>\s*</updateservers>#s', $replacement, $content);
|
|
$changes[] = 'Added update server URL to empty <updateservers>';
|
|
} elseif (!str_contains($content, '<updateservers>')) {
|
|
// No updateservers at all — add before </extension>
|
|
$serverBlock = "\n <updateservers>\n"
|
|
. " <server type=\"extension\" name=\"{$updateServerName}\">{$updateServerUrl}</server>\n"
|
|
. " </updateservers>\n";
|
|
$content = str_replace('</extension>', $serverBlock . '</extension>', $content);
|
|
$changes[] = 'Added <updateservers> block';
|
|
} else {
|
|
// updateservers exists — verify URL is correct
|
|
if (preg_match('#<server[^>]*>([^<]+)</server>#', $content, $m)) {
|
|
if ($m[1] !== $updateServerUrl) {
|
|
$content = preg_replace(
|
|
'#(<server[^>]*>)[^<]+(</server>)#',
|
|
"\${1}{$updateServerUrl}\${2}",
|
|
$content
|
|
);
|
|
$changes[] = "Updated server URL: {$m[1]} → {$updateServerUrl}";
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- 5b. Ensure <dlid> tag if required ---
|
|
if ($dlidEnabled) {
|
|
if (!str_contains($content, '<dlid')) {
|
|
// Add before <updateservers> if present, otherwise before </extension>
|
|
$dlidTag = ' <dlid prefix="dlid=" suffix=""/>' . "\n";
|
|
|
|
if (str_contains($content, '<updateservers>')) {
|
|
$content = str_replace('<updateservers>', $dlidTag . "\n <updateservers>", $content);
|
|
} else {
|
|
$content = str_replace('</extension>', $dlidTag . '</extension>', $content);
|
|
}
|
|
|
|
$changes[] = 'Added <dlid> tag';
|
|
}
|
|
}
|
|
|
|
// --- 5c. Ensure <blockChildUninstall> for packages ---
|
|
if (str_contains($content, 'type="package"') && !str_contains($content, '<blockChildUninstall>')) {
|
|
$blockTag = ' <blockChildUninstall>true</blockChildUninstall>' . "\n";
|
|
|
|
if (str_contains($content, '<dlid')) {
|
|
// Add after <dlid>
|
|
$content = preg_replace(
|
|
'#(<dlid[^/]*/>\s*\n)#',
|
|
"\${1}{$blockTag}",
|
|
$content
|
|
);
|
|
} elseif (str_contains($content, '<updateservers>')) {
|
|
$content = str_replace('<updateservers>', $blockTag . "\n <updateservers>", $content);
|
|
} else {
|
|
$content = str_replace('</extension>', $blockTag . '</extension>', $content);
|
|
}
|
|
|
|
$changes[] = 'Added <blockChildUninstall>true</blockChildUninstall>';
|
|
}
|
|
|
|
// ── 6. Report and apply ───────────────────────────────────────────
|
|
if (empty($changes)) {
|
|
$this->log('INFO', 'All licensing tags are correct — no changes needed');
|
|
$this->outputResult($ghOutput, 'ok', 'No changes needed');
|
|
return 0;
|
|
}
|
|
|
|
foreach ($changes as $change) {
|
|
$this->log($fix ? 'INFO' : 'WARN', ($fix ? 'Fixed: ' : 'Needs fix: ') . $change);
|
|
}
|
|
|
|
if ($fix) {
|
|
file_put_contents($packageManifest, $content);
|
|
$this->log('INFO', "Wrote {$relPath} with " . count($changes) . " change(s)");
|
|
$this->outputResult($ghOutput, 'fixed', implode('; ', $changes));
|
|
} else {
|
|
$this->log('WARN', 'Run with --fix to apply changes');
|
|
$this->outputResult($ghOutput, 'needs-fix', implode('; ', $changes));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Write result to $GITHUB_OUTPUT if requested.
|
|
*/
|
|
private function outputResult(bool $ghOutput, string $status, string $detail): void
|
|
{
|
|
if (!$ghOutput) {
|
|
return;
|
|
}
|
|
|
|
$outputFile = getenv('GITHUB_OUTPUT');
|
|
|
|
if ($outputFile === false || $outputFile === '') {
|
|
echo "licensing_status={$status}\n";
|
|
echo "licensing_detail={$detail}\n";
|
|
return;
|
|
}
|
|
|
|
$fh = fopen($outputFile, 'a');
|
|
fwrite($fh, "licensing_status={$status}\n");
|
|
fwrite($fh, "licensing_detail={$detail}\n");
|
|
fclose($fh);
|
|
}
|
|
}
|
|
|
|
$app = new ManifestLicensingCli();
|
|
exit($app->execute());
|