* * 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"); } } }