2026-06-11 18:16:18 -05:00
|
|
|
#!/usr/bin/env php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
*
|
|
|
|
|
* FILE INFORMATION
|
|
|
|
|
* DEFGROUP: mokoplatform.CLI
|
|
|
|
|
* INGROUP: mokoplatform
|
|
|
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
2026-06-11 18:20:57 -05:00
|
|
|
* PATH: /cli/metadata_read.php
|
2026-06-11 23:25:41 +00:00
|
|
|
* VERSION: 09.26.01
|
2026-06-11 18:20:57 -05:00
|
|
|
* BRIEF: Read and set metadata fields in .mokogitea/metadata.xml (or manifest.xml)
|
2026-06-11 18:16:18 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
|
|
|
|
|
|
|
|
|
use MokoEnterprise\CliFramework;
|
|
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
/** Field name → XPath mapping into the metadata XML */
|
|
|
|
|
const FIELD_MAP = [
|
|
|
|
|
// identity
|
|
|
|
|
'name' => 'identity/name',
|
|
|
|
|
'display-name' => 'identity/display-name',
|
|
|
|
|
'org' => 'identity/org',
|
|
|
|
|
'description' => 'identity/description',
|
|
|
|
|
'license' => 'identity/license',
|
|
|
|
|
'version' => 'identity/version',
|
|
|
|
|
// governance
|
|
|
|
|
'platform' => 'governance/platform',
|
|
|
|
|
'standards-version' => 'governance/standards-version',
|
|
|
|
|
'standards-source' => 'governance/standards-source',
|
|
|
|
|
// build
|
|
|
|
|
'language' => 'build/language',
|
|
|
|
|
'package-type' => 'build/package-type',
|
|
|
|
|
'entry-point' => 'build/entry-point',
|
|
|
|
|
// deploy
|
|
|
|
|
'source-dir' => 'deploy/source-dir',
|
|
|
|
|
'remote-subdir' => 'deploy/remote-subdir',
|
|
|
|
|
'excludes' => 'deploy/excludes',
|
|
|
|
|
'dev-host' => 'deploy/dev-host',
|
|
|
|
|
'demo-host' => 'deploy/demo-host',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
class MetadataReadCli extends CliFramework
|
2026-06-11 18:16:18 -05:00
|
|
|
{
|
|
|
|
|
protected function configure(): void
|
|
|
|
|
{
|
2026-06-11 18:20:57 -05:00
|
|
|
$this->setDescription('Read or set metadata fields in .mokogitea/metadata.xml');
|
2026-06-11 18:16:18 -05:00
|
|
|
$this->addArgument('--path', 'Repository root path', '.');
|
2026-06-11 18:20:57 -05:00
|
|
|
$this->addArgument('--field', 'Single field name to read', '');
|
|
|
|
|
$this->addArgument('--set', 'Set field value (field=value), repeatable', '');
|
2026-06-11 18:16:18 -05:00
|
|
|
$this->addArgument('--all', 'Print all fields as KEY=VALUE lines', false);
|
|
|
|
|
$this->addArgument('--github-output', 'Append all fields to $GITHUB_OUTPUT', false);
|
|
|
|
|
$this->addArgument('--json', 'Output all fields as JSON', false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function run(): int
|
|
|
|
|
{
|
2026-06-11 18:20:57 -05:00
|
|
|
$path = $this->getArgument('--path');
|
|
|
|
|
$field = $this->getArgument('--field');
|
|
|
|
|
$setValue = $this->getArgument('--set');
|
2026-06-11 18:16:18 -05:00
|
|
|
$showAll = $this->getArgument('--all');
|
|
|
|
|
$ghOutput = $this->getArgument('--github-output');
|
|
|
|
|
$jsonMode = $this->getArgument('--json');
|
|
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
$root = realpath($path) ?: $path;
|
|
|
|
|
|
|
|
|
|
// -- Locate metadata file --
|
|
|
|
|
$metadataFile = $this->findMetadataFile($root);
|
|
|
|
|
|
|
|
|
|
if ($metadataFile === null) {
|
|
|
|
|
$this->log('ERROR', "No metadata file found in {$root}");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Auto-migrate manifest.xml → metadata.xml --
|
|
|
|
|
$metadataFile = $this->migrateIfNeeded($metadataFile, $root);
|
|
|
|
|
|
|
|
|
|
// -- Set mode --
|
|
|
|
|
if ($setValue !== '') {
|
|
|
|
|
return $this->handleSet($metadataFile, $setValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Read mode --
|
|
|
|
|
$xml = @simplexml_load_file($metadataFile);
|
|
|
|
|
|
|
|
|
|
if ($xml === false) {
|
|
|
|
|
// Fallback: legacy YAML format (.mokoplatform)
|
|
|
|
|
$fields = $this->parseLegacy($metadataFile);
|
2026-06-11 18:16:18 -05:00
|
|
|
} else {
|
2026-06-11 18:20:57 -05:00
|
|
|
$fields = $this->parseXml($xml, $metadataFile);
|
2026-06-11 18:16:18 -05:00
|
|
|
}
|
|
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
$fields = array_filter($fields, fn($v) => $v !== '');
|
|
|
|
|
|
|
|
|
|
return $this->outputFields($fields, $field, $showAll, $ghOutput, $jsonMode);
|
|
|
|
|
}
|
2026-06-11 18:16:18 -05:00
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
private function findMetadataFile(string $root): ?string
|
|
|
|
|
{
|
2026-06-11 18:16:18 -05:00
|
|
|
$candidates = [
|
2026-06-11 18:20:57 -05:00
|
|
|
"{$root}/.mokogitea/metadata.xml",
|
2026-06-11 18:16:18 -05:00
|
|
|
"{$root}/.mokogitea/manifest.xml",
|
2026-06-11 18:20:57 -05:00
|
|
|
"{$root}/.mokogitea/.manifest.xml",
|
|
|
|
|
"{$root}/.mokogitea/.mokoplatform",
|
2026-06-11 18:16:18 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($candidates as $candidate) {
|
|
|
|
|
if (file_exists($candidate)) {
|
2026-06-11 18:20:57 -05:00
|
|
|
return $candidate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function migrateIfNeeded(string $metadataFile, string $root): string
|
|
|
|
|
{
|
|
|
|
|
$newPath = "{$root}/.mokogitea/metadata.xml";
|
|
|
|
|
|
|
|
|
|
// Already at the new location
|
|
|
|
|
if ($metadataFile === $newPath) {
|
|
|
|
|
return $metadataFile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy file found — migrate
|
|
|
|
|
if (str_ends_with($metadataFile, '.mokoplatform')) {
|
|
|
|
|
// YAML legacy — can't auto-migrate, just warn
|
|
|
|
|
$this->log('WARN', "Legacy .mokoplatform format detected — migrate to metadata.xml manually");
|
|
|
|
|
return $metadataFile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// manifest.xml or .manifest.xml → metadata.xml
|
|
|
|
|
copy($metadataFile, $newPath);
|
|
|
|
|
unlink($metadataFile);
|
|
|
|
|
$this->log('INFO', "Migrated " . basename($metadataFile) . " → metadata.xml");
|
|
|
|
|
return $newPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function parseXml(\SimpleXMLElement $xml, string $filePath): array
|
|
|
|
|
{
|
|
|
|
|
$fields = [];
|
|
|
|
|
foreach (FIELD_MAP as $name => $xpath) {
|
|
|
|
|
$parts = explode('/', $xpath);
|
|
|
|
|
$node = $xml;
|
|
|
|
|
foreach ($parts as $part) {
|
|
|
|
|
$node = $node->{$part} ?? null;
|
|
|
|
|
if ($node === null) break;
|
|
|
|
|
}
|
|
|
|
|
if ($name === 'license' && $node !== null) {
|
|
|
|
|
// Also extract spdx attribute
|
|
|
|
|
$fields['license'] = (string)$node;
|
|
|
|
|
$fields['license-spdx'] = (string)($node['spdx'] ?? '');
|
|
|
|
|
} else {
|
|
|
|
|
$fields[$name] = $node !== null ? (string)$node : '';
|
2026-06-11 18:16:18 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-11 18:20:57 -05:00
|
|
|
$fields['metadata-file'] = $filePath;
|
|
|
|
|
return $fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function parseLegacy(string $filePath): array
|
|
|
|
|
{
|
|
|
|
|
$content = file_get_contents($filePath);
|
|
|
|
|
$fields = [];
|
|
|
|
|
if (preg_match('/^platform:\s*(.+)/m', $content, $m)) {
|
|
|
|
|
$fields['platform'] = trim($m[1], " \t\n\r\"'");
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^standards_version:\s*(.+)/m', $content, $m)) {
|
|
|
|
|
$fields['standards-version'] = trim($m[1], " \t\n\r\"'");
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^governed_repo:\s*(.+)/m', $content, $m)) {
|
|
|
|
|
$fields['name'] = trim($m[1], " \t\n\r\"'");
|
|
|
|
|
}
|
|
|
|
|
return $fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function handleSet(string $metadataFile, string $setValue): int
|
|
|
|
|
{
|
|
|
|
|
// Parse field=value pairs (comma-separated or from repeated --set)
|
|
|
|
|
$pairs = [];
|
|
|
|
|
foreach (explode(',', $setValue) as $pair) {
|
|
|
|
|
$pair = trim($pair);
|
|
|
|
|
if ($pair === '') continue;
|
|
|
|
|
$eq = strpos($pair, '=');
|
|
|
|
|
if ($eq === false) {
|
|
|
|
|
$this->log('ERROR', "Invalid set format: '{$pair}' — expected field=value");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
$key = trim(substr($pair, 0, $eq));
|
|
|
|
|
$val = trim(substr($pair, $eq + 1));
|
|
|
|
|
$pairs[$key] = $val;
|
|
|
|
|
}
|
2026-06-11 18:16:18 -05:00
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
if (empty($pairs)) {
|
|
|
|
|
$this->log('ERROR', 'No field=value pairs provided');
|
2026-06-11 18:16:18 -05:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
// Validate all fields exist in FIELD_MAP
|
|
|
|
|
foreach ($pairs as $key => $val) {
|
|
|
|
|
if (!isset(FIELD_MAP[$key])) {
|
|
|
|
|
$this->log('ERROR', "Unknown field: '{$key}'");
|
|
|
|
|
$this->log('INFO', 'Valid fields: ' . implode(', ', array_keys(FIELD_MAP)));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy files are read-only
|
|
|
|
|
if (str_ends_with($metadataFile, '.mokoplatform')) {
|
|
|
|
|
$this->log('ERROR', 'Cannot set fields on legacy .mokoplatform format — migrate to metadata.xml first');
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2026-06-11 18:16:18 -05:00
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
// Load XML
|
|
|
|
|
$xml = @simplexml_load_file($metadataFile);
|
2026-06-11 18:16:18 -05:00
|
|
|
if ($xml === false) {
|
2026-06-11 18:20:57 -05:00
|
|
|
$this->log('ERROR', "Failed to parse XML: {$metadataFile}");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set each field
|
|
|
|
|
foreach ($pairs as $key => $val) {
|
|
|
|
|
$xpath = FIELD_MAP[$key];
|
|
|
|
|
$parts = explode('/', $xpath);
|
|
|
|
|
$section = $parts[0];
|
|
|
|
|
$element = $parts[1];
|
|
|
|
|
|
|
|
|
|
if (!isset($xml->{$section})) {
|
|
|
|
|
$this->log('ERROR', "Section <{$section}> not found in XML — cannot set '{$key}'");
|
|
|
|
|
return 1;
|
2026-06-11 18:16:18 -05:00
|
|
|
}
|
2026-06-11 18:20:57 -05:00
|
|
|
|
|
|
|
|
if (!isset($xml->{$section}->{$element})) {
|
|
|
|
|
$this->log('ERROR', "Element <{$element}> not found in <{$section}> — cannot set '{$key}'");
|
|
|
|
|
return 1;
|
2026-06-11 18:16:18 -05:00
|
|
|
}
|
2026-06-11 18:20:57 -05:00
|
|
|
|
|
|
|
|
$old = (string)$xml->{$section}->{$element};
|
|
|
|
|
$xml->{$section}->{$element} = $val;
|
|
|
|
|
$this->log('INFO', "Set {$key}: '{$old}' → '{$val}'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write back with preserved formatting
|
|
|
|
|
$dom = new \DOMDocument('1.0', 'UTF-8');
|
|
|
|
|
$dom->preserveWhiteSpace = false;
|
|
|
|
|
$dom->formatOutput = true;
|
|
|
|
|
$dom->loadXML($xml->asXML());
|
|
|
|
|
$dom->save($metadataFile);
|
|
|
|
|
|
|
|
|
|
$this->log('INFO', "Updated {$metadataFile}");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function outputFields(array $fields, string $field, $showAll, $ghOutput, $jsonMode): int
|
|
|
|
|
{
|
|
|
|
|
if ($ghOutput) {
|
|
|
|
|
$mode = 'github-output';
|
|
|
|
|
} elseif ($showAll) {
|
|
|
|
|
$mode = 'all';
|
|
|
|
|
} elseif ($jsonMode) {
|
|
|
|
|
$mode = 'json';
|
2026-06-11 18:16:18 -05:00
|
|
|
} else {
|
2026-06-11 18:20:57 -05:00
|
|
|
$mode = 'field';
|
|
|
|
|
}
|
2026-06-11 18:16:18 -05:00
|
|
|
|
|
|
|
|
switch ($mode) {
|
|
|
|
|
case 'field':
|
|
|
|
|
if ($field === '') {
|
2026-06-11 18:20:57 -05:00
|
|
|
$this->log('ERROR', "Usage: metadata_read.php --path <dir> --field <name>");
|
|
|
|
|
$this->log('ERROR', " metadata_read.php --path <dir> --all");
|
|
|
|
|
$this->log('ERROR', " metadata_read.php --path <dir> --json");
|
|
|
|
|
$this->log('ERROR', " metadata_read.php --path <dir> --set field=value");
|
2026-06-11 18:16:18 -05:00
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
echo ($fields[$field] ?? '') . "\n";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'all':
|
|
|
|
|
foreach ($fields as $k => $v) {
|
|
|
|
|
echo "{$k}={$v}\n";
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'json':
|
|
|
|
|
echo json_encode($fields, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'github-output':
|
|
|
|
|
$outputFile = getenv('GITHUB_OUTPUT');
|
|
|
|
|
if ($outputFile === false || $outputFile === '') {
|
|
|
|
|
$this->log('ERROR', 'GITHUB_OUTPUT not set — printing to stdout instead');
|
|
|
|
|
foreach ($fields as $k => $v) {
|
|
|
|
|
$envKey = str_replace('-', '_', $k);
|
|
|
|
|
echo "{$envKey}={$v}\n";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$fh = fopen($outputFile, 'a');
|
|
|
|
|
foreach ($fields as $k => $v) {
|
|
|
|
|
$envKey = str_replace('-', '_', $k);
|
|
|
|
|
fwrite($fh, "{$envKey}={$v}\n");
|
|
|
|
|
}
|
|
|
|
|
fclose($fh);
|
|
|
|
|
$this->log('INFO', "Wrote " . count($fields) . " fields to GITHUB_OUTPUT");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 18:20:57 -05:00
|
|
|
$app = new MetadataReadCli();
|
2026-06-11 18:16:18 -05:00
|
|
|
exit($app->execute());
|