ca55e5d2d2
Introduces SourceResolver utility class with source/ → src/ → htdocs/ fallback chain, replacing hardcoded src/ references across 28 files. This enables renaming root-level src/ to source/ in all repos while maintaining backwards compatibility during the transition. Phase 1: New lib/Enterprise/SourceResolver.php with resolve(), resolveAbsolute(), globSource(), findUnderSource(), warnIfLegacy() Phase 2: Updated 19 CLI/deploy tools to use SourceResolver Phase 3: Updated 7 validator/lib files (McpServerPlugin, PackageBuilder, RepositorySynchronizer, auto_detect_platform, check_dolibarr_module, check_client_theme, check_structure) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
6.0 KiB
PHP
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/moko-platform
|
|
* 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");
|
|
}
|
|
}
|
|
}
|