Files
Jonathan Miller b73c1eba25
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Platform: mokoplatform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Security Audit / Dependency Audit (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Failing after 11s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 21s
Generic: Project CI / Lint & Validate (pull_request) Failing after 32s
Platform: mokoplatform CI / Gate 1: Code Quality (pull_request) Failing after 1m31s
feat: add manifest_detect.php CLI tool for auto-detecting manifest fields
Scans source files to detect platform, name, version, element_name,
package_type, language, entry_point, description, and license_spdx.
Supports Joomla, Dolibarr, Go, MCP/Node, and generic platforms.

Includes --diff and --update modes for comparing against and pushing
to the Gitea manifest API. Warns on missing core fields.

Also removes deprecated mcp/servers/mokowaas_api (consolidated to
separate repo) and syncs dev branch changes.
2026-06-07 15:37:24 -05:00

188 lines
6.0 KiB
PHP

<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoPlatform.Enterprise
* INGROUP: MokoPlatform.Lib
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
* PATH: /lib/Enterprise/SourceResolver.php
* BRIEF: Resolve the root-level source directory across repos (source/, src/, htdocs/)
*/
declare(strict_types=1);
namespace MokoEnterprise;
/**
* Source Directory Resolver
*
* Provides a single, consistent fallback chain for locating the root-level
* source directory in any MokoStandards repository. The preferred directory
* is `source/`, with legacy `src/` and `htdocs/` as fallbacks.
*
* This class exists because Joomla extensions use `src/` for namespace
* autoloading (e.g. administrator/components/com_foo/src/). Renaming our
* root-level source directory to `source/` avoids that collision. During
* the transition period, repos may still use `src/`, so all tooling must
* check both.
*
* Usage:
* $dir = SourceResolver::resolve($repoRoot); // 'source', 'src', or 'htdocs'
* $abs = SourceResolver::resolveAbsolute($repoRoot); // full path or null
* $xmls = SourceResolver::globSource($repoRoot, '*.xml'); // glob under first match
* $path = SourceResolver::findUnderSource($repoRoot, 'core/modules'); // subpath lookup
*
* @since 09.02.00
*/
class SourceResolver
{
/**
* Ordered candidate directories. source/ is preferred, src/ is legacy fallback.
*
* When the migration is complete and all repos use source/, the 'src'
* entry can be removed from this list.
*
* @var string[]
*/
private const CANDIDATES = ['source', 'src', 'htdocs'];
/**
* Resolve the source directory name for a repository root.
*
* Returns the first candidate directory that exists, or 'source' as the
* default when no candidate is found (e.g. for new repos being scaffolded).
*
* @param string $root Absolute path to the repository root.
* @return string Directory name (e.g. 'source', 'src', 'htdocs').
*/
public static function resolve(string $root): string
{
foreach (self::CANDIDATES as $candidate) {
if (is_dir("{$root}/{$candidate}")) {
return $candidate;
}
}
return 'source';
}
/**
* Resolve the source directory as an absolute path.
*
* @param string $root Absolute path to the repository root.
* @return string|null Absolute path to the source directory, or null if none exists.
*/
public static function resolveAbsolute(string $root): ?string
{
foreach (self::CANDIDATES as $candidate) {
$path = "{$root}/{$candidate}";
if (is_dir($path)) {
return $path;
}
}
return null;
}
/**
* Glob for files under the source directory.
*
* Checks each candidate directory in order and returns matches from the
* first candidate that produces results. This replaces patterns like:
*
* glob("{$root}/src/*.xml")
*
* With the backwards-compatible:
*
* SourceResolver::globSource($root, '*.xml')
*
* @param string $root Absolute path to the repository root.
* @param string $pattern Glob pattern relative to the source directory.
* @return string[] Matched file paths (may be empty).
*/
public static function globSource(string $root, string $pattern): array
{
foreach (self::CANDIDATES as $candidate) {
$dir = "{$root}/{$candidate}";
if (!is_dir($dir)) {
continue;
}
$matches = glob("{$dir}/{$pattern}") ?: [];
if ($matches !== []) {
return $matches;
}
}
return [];
}
/**
* Find a subpath under any source directory candidate.
*
* Useful for locating platform-specific subdirectories like
* `core/modules/` (Dolibarr) or `media/templates/` (Joomla client themes)
* regardless of whether the repo uses `source/` or `src/`.
*
* @param string $root Absolute path to the repository root.
* @param string $subpath Relative path to look for (e.g. 'core/modules', 'index.ts').
* @return string|null Absolute path if found, null otherwise.
*/
public static function findUnderSource(string $root, string $subpath): ?string
{
foreach (self::CANDIDATES as $candidate) {
$full = "{$root}/{$candidate}/{$subpath}";
if (file_exists($full) || is_dir($full)) {
return $full;
}
}
return null;
}
/**
* Get the ordered list of candidate directory names.
*
* Useful for workflows or scripts that need to iterate candidates
* themselves (e.g. building find/grep patterns).
*
* @return string[]
*/
public static function getCandidates(): array
{
return self::CANDIDATES;
}
/**
* Check whether the resolved source directory is a legacy name (src/).
*
* @param string $root Absolute path to the repository root.
* @return bool True if the repo uses src/ instead of source/.
*/
public static function isLegacy(string $root): bool
{
$resolved = self::resolve($root);
return $resolved === 'src';
}
/**
* Emit a deprecation warning to stderr if the repo still uses src/.
*
* CLI tools should call this after resolving the source directory so
* that maintainers know to rename src/ → source/.
*
* @param string $root Absolute path to the repository root.
*/
public static function warnIfLegacy(string $root): void
{
if (self::isLegacy($root)) {
fwrite(STDERR, "⚠ WARNING: This repo uses src/ which is deprecated. Rename to source/ per MokoStandards.\n");
}
}
}