Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f441a8a51f | |||
| 005eb5cf39 | |||
| 21acb19fed | |||
| 1fe4f83e73 | |||
| 7e5c322792 | |||
| b010677d75 | |||
| 9275e581c2 | |||
| 3f3b1f79a0 | |||
| 83842c50ad | |||
| fbedd5966c | |||
| eca2c13018 | |||
| 48d000107d | |||
| 7ceb9528cc | |||
| 5fabaec477 |
@@ -124,16 +124,16 @@ jobs:
|
||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "PHPStan (Level 2)"
|
||||
continue-on-error: true
|
||||
- name: "PHPStan (Level 6)"
|
||||
run: |
|
||||
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=github 2>&1 || {
|
||||
echo "::warning::PHPStan found type errors (advisory)"
|
||||
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --memory-limit=512M --error-format=github 2>&1 || {
|
||||
echo "::error::PHPStan found type errors"
|
||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
}
|
||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Static analysis: advisory (level 0)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Static analysis (level 6): passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Psalm"
|
||||
continue-on-error: true
|
||||
@@ -177,11 +177,14 @@ jobs:
|
||||
|
||||
- name: "PHPUnit (PHP ${{ matrix.php }})"
|
||||
run: |
|
||||
vendor/bin/phpunit --testdox 2>&1
|
||||
{
|
||||
echo "### PHPUnit (PHP ${{ matrix.php }})"
|
||||
echo "All tests passed."
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
vendor/bin/phpunit --testdox 2>&1 || {
|
||||
echo "::error::PHPUnit tests failed"
|
||||
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests failed. Run \`vendor/bin/phpunit --testdox\` locally." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
}
|
||||
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 3 — Self-Health (Dogfood)
|
||||
|
||||
@@ -18,6 +18,19 @@ Version format: `XX.YY.ZZ` (zero-padded semver).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [09.00.00] - 2026-05-26
|
||||
|
||||
### Added
|
||||
- PHPDoc on Priority 1 Enterprise classes (CliFramework, adapters, ApiClient)
|
||||
- Wiki: Coding-Standards page with PHPDoc standard, PHPCS exclusions, file patterns
|
||||
- CI: PHPStan enforced at level 6 (was advisory), PHPUnit blocks on failure
|
||||
|
||||
### Fixed
|
||||
- `updates_xml_build.php`: cascade entries down to lower channels — stable now writes all 5 entries instead of wiping them
|
||||
- `updates_xml_build.php`: separate Joomla stability tags (`dev`, `rc`) from Gitea release tags (`development`, `release-candidate`) — download URLs now point to correct release assets
|
||||
- `updates_xml_build.php`: only emit `<client>site</client>` for templates and modules, not packages or components
|
||||
- `updates_xml_build.php`: preservation logic matches Joomla tag names when deciding which existing entries to keep
|
||||
|
||||
## [08.00.00] - 2026-05-26
|
||||
|
||||
### Changed
|
||||
|
||||
+48
-41
@@ -194,8 +194,7 @@ $stabilitySuffixMap = [
|
||||
'development' => '-dev',
|
||||
];
|
||||
|
||||
// Joomla's stabilityTagToInteger() maps these to STABILITY_* constants.
|
||||
// MUST use 'dev' not 'development' — STABILITY_DEVELOPMENT does not exist.
|
||||
// Joomla <tags><tag> values — maps to Joomla's stabilityTagToInteger()
|
||||
$stabilityTagMap = [
|
||||
'stable' => 'stable',
|
||||
'rc' => 'rc',
|
||||
@@ -204,19 +203,22 @@ $stabilityTagMap = [
|
||||
'development' => 'dev',
|
||||
];
|
||||
|
||||
// -- Build update entries -----------------------------------------------------
|
||||
$releaseTag = $stabilityTagMap[$stability] ?? $stability;
|
||||
// Gitea release tag names (used in download/info URLs)
|
||||
$releaseTagMap = [
|
||||
'stable' => 'stable',
|
||||
'rc' => 'release-candidate',
|
||||
'beta' => 'beta',
|
||||
'alpha' => 'alpha',
|
||||
'development' => 'development',
|
||||
];
|
||||
|
||||
// -- Build update entries -----------------------------------------------------
|
||||
// For the primary entry: apply suffix if not stable
|
||||
$primarySuffix = $stabilitySuffixMap[$stability] ?? '';
|
||||
$primaryVersion = $version . $primarySuffix;
|
||||
|
||||
$downloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$releaseTag}/{$typePrefix}{$extElement}-{$primaryVersion}.zip";
|
||||
$infoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$releaseTag}";
|
||||
|
||||
// Build client tag — Joomla defaults to client_id=1 (administrator) when missing.
|
||||
// Packages install with client_id=0 (site), so we MUST include <client>site</client>
|
||||
// for all types to prevent a mismatch that causes extension_id=0 in #__updates.
|
||||
// Build client tag — Joomla requires <client>site</client> to match updates
|
||||
// to installed extensions. Without it, extension_id=0 in #__updates.
|
||||
$clientTag = '';
|
||||
if (!empty($extClient)) {
|
||||
$clientTag = " <client>{$extClient}</client>";
|
||||
@@ -286,41 +288,44 @@ function buildEntry(
|
||||
}
|
||||
|
||||
// -- Determine which channels to write ----------------------------------------
|
||||
// Stable cascades to all channels; pre-releases only write their level and below
|
||||
// Each channel gets its own suffixed version:
|
||||
// development -> 04.01.00-dev
|
||||
// alpha -> 04.01.00-alpha
|
||||
// beta -> 04.01.00-beta
|
||||
// rc -> 04.01.00-rc
|
||||
// stable -> 04.01.00
|
||||
// Stable cascades to all channels; pre-releases cascade down to lower channels.
|
||||
// Each channel entry represents "latest release available at this stability or higher".
|
||||
// When stable releases, ALL channels point to stable (it's the newest for everyone).
|
||||
// When RC releases, rc/beta/alpha/dev point to RC; stable is preserved.
|
||||
// When dev releases, only dev is updated; everything else is preserved.
|
||||
$allChannels = ['development', 'alpha', 'beta', 'rc', 'stable'];
|
||||
$stabilityIndex = array_search($stability === 'development' ? 'development' : $stability, $allChannels);
|
||||
if ($stabilityIndex === false) $stabilityIndex = 4; // default to stable
|
||||
|
||||
// Write only the current channel entry (not cascade)
|
||||
// Each channel release only creates its own entry; preserved entries handle other channels
|
||||
// Write entries for the current channel AND all lower channels (cascade down)
|
||||
// All cascaded entries point to the CURRENT release (the highest stability being built)
|
||||
$entries = [];
|
||||
$channelName = $allChannels[$stabilityIndex];
|
||||
$channelSuffix = $stabilitySuffixMap[$channelName] ?? '';
|
||||
$channelVersion = $version . $channelSuffix;
|
||||
$channelTag = $stabilityTagMap[$channelName] ?? $channelName;
|
||||
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$channelTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
||||
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$channelTag}";
|
||||
$giteaTag = $releaseTagMap[$stability] ?? $stability;
|
||||
$channelVersion = $version . ($stabilitySuffixMap[$stability] ?? '');
|
||||
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$giteaTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
|
||||
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$giteaTag}";
|
||||
|
||||
$entries[] = buildEntry(
|
||||
$channelName,
|
||||
$channelVersion,
|
||||
$channelDownloadUrl,
|
||||
$extName,
|
||||
$extElement,
|
||||
$extType,
|
||||
$clientTag,
|
||||
$folderTag,
|
||||
$channelInfoUrl,
|
||||
$targetPlatform,
|
||||
$phpTag,
|
||||
$shaTag
|
||||
);
|
||||
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
||||
$channelName = $allChannels[$i];
|
||||
$joomlaTag = $stabilityTagMap[$channelName] ?? $channelName;
|
||||
// Only attach SHA to the primary channel entry
|
||||
$entrySha = ($i === $stabilityIndex) ? $shaTag : '';
|
||||
|
||||
$entries[] = buildEntry(
|
||||
$joomlaTag,
|
||||
$channelVersion,
|
||||
$channelDownloadUrl,
|
||||
$extName,
|
||||
$extElement,
|
||||
$extType,
|
||||
$clientTag,
|
||||
$folderTag,
|
||||
$channelInfoUrl,
|
||||
$targetPlatform,
|
||||
$phpTag,
|
||||
$entrySha
|
||||
);
|
||||
}
|
||||
|
||||
// -- Preserve existing entries for channels not being updated -----------------
|
||||
$dest = $outputFile ?? "{$root}/updates.xml";
|
||||
@@ -329,11 +334,13 @@ $preservedEntries = [];
|
||||
if (file_exists($dest)) {
|
||||
$existingXml = @simplexml_load_file($dest);
|
||||
if ($existingXml) {
|
||||
// Channels we're writing — don't preserve these
|
||||
// Joomla tags we're writing — don't preserve these
|
||||
$writtenChannels = [];
|
||||
for ($i = 0; $i <= $stabilityIndex; $i++) {
|
||||
$writtenChannels[] = $allChannels[$i];
|
||||
$writtenChannels[] = $stabilityTagMap[$allChannels[$i]] ?? $allChannels[$i];
|
||||
}
|
||||
// Also match legacy/alternate tag names (e.g. 'development' = 'dev')
|
||||
$writtenChannels[] = 'development'; // alias for 'dev'
|
||||
|
||||
foreach ($existingXml->update as $existingUpdate) {
|
||||
$existingTag = '';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "mokoconsulting-tech/enterprise",
|
||||
"description": "MokoStandards Enterprise API \u2014 PHP implementation",
|
||||
"type": "library",
|
||||
"version": "08.00.00",
|
||||
"version": "09.00.00",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -92,6 +92,8 @@ class CircuitBreakerOpen extends RuntimeException
|
||||
* );
|
||||
* $response = $client->get('/repos/owner/repo');
|
||||
* ```
|
||||
*
|
||||
* @since 04.00.00
|
||||
*/
|
||||
class ApiClient
|
||||
{
|
||||
|
||||
@@ -716,6 +716,9 @@ class ValidationCLI extends CLIApp
|
||||
* Lifecycle: configure() -> parseArguments() -> printBanner() -> initialize() -> run()
|
||||
*
|
||||
* All new scripts must extend CliFramework and implement configure() + run().
|
||||
*
|
||||
* @since 04.00.15
|
||||
* @see CLIApp Legacy base class (deprecated)
|
||||
*/
|
||||
abstract class CliFramework
|
||||
{
|
||||
@@ -932,6 +935,11 @@ abstract class CliFramework
|
||||
// Argument parsing (internal)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Parse CLI arguments from $_SERVER['argv'] into registered argument definitions.
|
||||
*
|
||||
* @since 04.00.15
|
||||
*/
|
||||
private function parseArguments(): void
|
||||
{
|
||||
$argv = array_slice($_SERVER['argv'] ?? [], 1);
|
||||
@@ -970,6 +978,11 @@ abstract class CliFramework
|
||||
// Help screen
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Print auto-generated help screen from registered arguments.
|
||||
*
|
||||
* @since 04.00.15
|
||||
*/
|
||||
protected function printHelp(): void
|
||||
{
|
||||
$w = $this->termWidth();
|
||||
|
||||
@@ -32,12 +32,17 @@ use RuntimeException;
|
||||
* - Workflow dir: .github/workflows
|
||||
*
|
||||
* @package MokoStandards\Enterprise
|
||||
* @version 04.06.10
|
||||
* @since 04.06.10
|
||||
* @see GitPlatformAdapter
|
||||
*/
|
||||
class GitHubAdapter implements GitPlatformAdapter
|
||||
{
|
||||
/** @var ApiClient HTTP client for GitHub API calls. */
|
||||
private ApiClient $apiClient;
|
||||
|
||||
/**
|
||||
* @param ApiClient $apiClient Configured API client for api.github.com
|
||||
*/
|
||||
public function __construct(ApiClient $apiClient)
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
|
||||
@@ -34,13 +34,21 @@ use RuntimeException;
|
||||
* - Workflow dir: .mokogitea/workflows
|
||||
*
|
||||
* @package MokoStandards\Enterprise
|
||||
* @version 04.06.10
|
||||
* @since 04.06.10
|
||||
* @see GitPlatformAdapter
|
||||
*/
|
||||
class MokoGiteaAdapter implements GitPlatformAdapter
|
||||
{
|
||||
/** @var ApiClient HTTP client for Gitea API calls. */
|
||||
private ApiClient $apiClient;
|
||||
|
||||
/** @var string Base URL for Gitea API (e.g. https://git.mokoconsulting.tech/api/v1). */
|
||||
private string $baseUrl;
|
||||
|
||||
/**
|
||||
* @param ApiClient $apiClient Configured API client
|
||||
* @param string $baseUrl Gitea API base URL
|
||||
*/
|
||||
public function __construct(ApiClient $apiClient, string $baseUrl = 'https://git.mokoconsulting.tech/api/v1')
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
|
||||
@@ -415,15 +415,9 @@ parameters:
|
||||
path: cli/theme_lint.php
|
||||
|
||||
-
|
||||
message: '#^Offset ''alpha''\|''beta''\|''development''\|''rc''\|''stable'' on array\{stable\: '''', rc\: ''\-rc'', beta\: ''\-beta'', alpha\: ''\-alpha'', development\: ''\-dev''\} on left side of \?\? always exists and is not nullable\.$#'
|
||||
message: '#^Offset ''alpha''\|''beta''\|''development''\|''rc''\|''stable'' on array\{stable\: ''stable'', rc\: ''rc'', beta\: ''beta'', alpha\: ''alpha'', development\: ''dev''\} on left side of \?\? always exists and is not nullable\.$#'
|
||||
identifier: nullCoalesce.offset
|
||||
count: 1
|
||||
path: cli/updates_xml_build.php
|
||||
|
||||
-
|
||||
message: '#^Offset ''alpha''\|''beta''\|''development''\|''rc''\|''stable'' on array\{stable\: ''stable'', rc\: ''rc'', beta\: ''beta'', alpha\: ''alpha'', development\: ''development''\} on left side of \?\? always exists and is not nullable\.$#'
|
||||
identifier: nullCoalesce.offset
|
||||
count: 1
|
||||
count: 2
|
||||
path: cli/updates_xml_build.php
|
||||
|
||||
-
|
||||
|
||||
Reference in New Issue
Block a user