2026-05-26 15:12:02 -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: 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<string, string> 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.
|
|
|
|
|
*
|
2026-06-06 08:58:52 -05:00
|
|
|
* Fallback chain: manifest entry-point → source/ → src/ → htdocs/ → 'source'.
|
|
|
|
|
* Uses SourceResolver for the directory fallback when no entry-point is set.
|
|
|
|
|
*
|
2026-05-26 15:12:02 -05:00
|
|
|
* @param string $root Repository root for existence checking
|
2026-06-06 08:58:52 -05:00
|
|
|
* @return string Resolved source directory path (e.g. 'source', 'src', 'htdocs')
|
2026-05-26 15:12:02 -05:00
|
|
|
*/
|
|
|
|
|
public function getSourceDir(string $root = ''): string
|
|
|
|
|
{
|
|
|
|
|
$entryPoint = $this->get('entry-point', '');
|
|
|
|
|
if ($entryPoint !== '') {
|
2026-06-06 08:58:52 -05:00
|
|
|
// Strip trailing filename (e.g. source/index.ts → source)
|
2026-05-26 15:12:02 -05:00
|
|
|
$dir = rtrim(dirname($entryPoint) === '.' ? $entryPoint : dirname($entryPoint), '/');
|
|
|
|
|
if ($root === '' || is_dir("{$root}/{$dir}")) {
|
|
|
|
|
return $dir;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 08:58:52 -05:00
|
|
|
// Fallback: use SourceResolver (source/ → src/ → htdocs/ → default 'source')
|
2026-05-26 15:12:02 -05:00
|
|
|
if ($root !== '') {
|
2026-06-06 08:58:52 -05:00
|
|
|
return SourceResolver::resolve($root);
|
2026-05-26 15:12:02 -05:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 08:58:52 -05:00
|
|
|
return 'source';
|
2026-05-26 15:12:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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<string, string>
|
|
|
|
|
*/
|
|
|
|
|
public function getAll(): array
|
|
|
|
|
{
|
|
|
|
|
return $this->fields;
|
|
|
|
|
}
|
|
|
|
|
}
|