#!/usr/bin/env php * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: moko-platform.Enterprise * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /lib/Enterprise/ManifestReader.php * BRIEF: Read and parse .mokogitea/manifest.xml — shared across all CLI tools */ declare(strict_types=1); namespace MokoEnterprise; /** * Manifest Reader * * Parses .mokogitea/manifest.xml and provides typed access to all fields. * Used by CLI tools and the Enterprise library to determine platform, * build configuration, and deployment settings from the repository manifest. * * @since 09.01.00 */ class ManifestReader { /** @var array Parsed manifest fields */ private array $fields = []; /** @var bool Whether a manifest was found and parsed */ private bool $loaded = false; /** * Load manifest from a repository root directory. * * @param string $root Repository root path * @return self */ public static function fromPath(string $root): self { $reader = new self(); $reader->load($root); return $reader; } /** * Load and parse the manifest file. * * @param string $root Repository root path */ public function load(string $root): void { $candidates = [ "{$root}/.mokogitea/manifest.xml", "{$root}/.mokogitea/.manifest.xml", "{$root}/.mokogitea/.moko-platform", ]; $manifestFile = null; foreach ($candidates as $candidate) { if (file_exists($candidate)) { $manifestFile = $candidate; break; } } if ($manifestFile === null) { return; } $xml = @simplexml_load_file($manifestFile); if ($xml === false) { // Fallback: YAML legacy format $content = file_get_contents($manifestFile); if (preg_match('/^platform:\s*(.+)/m', $content, $m)) { $this->fields['platform'] = trim($m[1], " \t\n\r\"'"); } $this->loaded = true; return; } $this->fields = [ 'name' => (string)($xml->identity->name ?? ''), 'org' => (string)($xml->identity->org ?? ''), 'description' => (string)($xml->identity->description ?? ''), 'license' => (string)($xml->identity->license ?? ''), 'license-spdx' => (string)($xml->identity->license['spdx'] ?? ''), 'version' => (string)($xml->identity->version ?? ''), 'platform' => (string)($xml->governance->platform ?? ''), 'standards-version' => (string)($xml->governance->{"standards-version"} ?? ''), 'language' => (string)($xml->build->language ?? ''), 'package-type' => (string)($xml->build->{"package-type"} ?? ''), 'entry-point' => (string)($xml->build->{"entry-point"} ?? ''), 'source-dir' => (string)($xml->deploy->{"source-dir"} ?? ''), 'remote-subdir' => (string)($xml->deploy->{"remote-subdir"} ?? ''), 'dev-host' => (string)($xml->deploy->{"dev-host"} ?? ''), 'demo-host' => (string)($xml->deploy->{"demo-host"} ?? ''), ]; // Strip empty values $this->fields = array_filter($this->fields, fn($v) => $v !== ''); $this->loaded = true; } /** * Whether a manifest was found and loaded. * * @return bool */ public function isLoaded(): bool { return $this->loaded; } /** * Get a single field value. * * @param string $key Field name (e.g. 'platform', 'package-type') * @param string $default Default value if field is missing * @return string */ public function get(string $key, string $default = ''): string { return $this->fields[$key] ?? $default; } /** * Get the platform slug, normalized to canonical values. * * @return string One of: joomla, dolibarr, generic, mcp, nodejs */ public function getPlatform(): string { $raw = $this->get('platform', 'generic'); return match ($raw) { 'waas-component' => 'joomla', 'crm-module' => 'dolibarr', default => $raw, }; } /** * Get the source/entry-point directory. * * Fallback chain: manifest entry-point → source/ → src/ → htdocs/ → 'source'. * Uses SourceResolver for the directory fallback when no entry-point is set. * * @param string $root Repository root for existence checking * @return string Resolved source directory path (e.g. 'source', 'src', 'htdocs') */ public function getSourceDir(string $root = ''): string { $entryPoint = $this->get('entry-point', ''); if ($entryPoint !== '') { // Strip trailing filename (e.g. source/index.ts → source) $dir = rtrim(dirname($entryPoint) === '.' ? $entryPoint : dirname($entryPoint), '/'); if ($root === '' || is_dir("{$root}/{$dir}")) { return $dir; } } // Fallback: use SourceResolver (source/ → src/ → htdocs/ → default 'source') if ($root !== '') { return SourceResolver::resolve($root); } return 'source'; } /** * Get the package type for build decisions. * * @return string e.g. 'package', 'dolibarr', 'generic', 'mcp-server' */ public function getPackageType(): string { return $this->get('package-type', 'generic'); } /** * Get all parsed fields. * * @return array */ public function getAll(): array { return $this->fields; } }