236 Commits

Author SHA1 Message Date
jmiller 54196c833b fix(ci): harden branch-cleanup.yml against Actions injection (sync from Template-Generic) 2026-07-15 03:24:42 +00:00
jmiller ab7fe287a6 fix(ci): harden issue-branch.yml against Actions injection (sync from Template-Generic) 2026-07-15 03:24:40 +00:00
jmiller 3cdf1f564a Merge pull request 'chore(rebrand): fix residual gitea (space-path + named files)' (#157) from chore/rebrand-gitea-residual into main 2026-07-15 01:39:53 +00:00
jmiller c5fce57776 chore(rebrand): fix residual gitea (space-path files + gitea-named files)
Claude-Session: https://claude.ai/code/session_01DQEMmJPe61ya7HDfA6BHP8
2026-07-14 20:39:49 -05:00
jmiller 11dcbd31c1 Merge pull request 'chore(rebrand): gitea -> git (MokoGIT, .mokogitea -> .mokogit)' (#156) from chore/rebrand-gitea-to-git into main 2026-07-15 01:21:05 +00:00
jmiller 0404202d8d chore(rebrand): gitea -> git (MokoGIT brand, .mokogitea -> .mokogit)
Case-sensitive gitea->git: brand MokoGitea->MokoGIT, MOKOGITEA_TOKEN->
MOKOGIT_TOKEN, GITEA_URL/ORG/REPO->GIT_*, gitea-actions[bot]->git-actions,
ntfy gitea-*->git-*, .mokogitea/->.mokogit/. Protected: literal .gitea and
git.mokoconsulting.tech.

Claude-Session: https://claude.ai/code/session_01DQEMmJPe61ya7HDfA6BHP8
2026-07-14 20:21:01 -05:00
jmiller af6a51553e chore: sync version-set.yml from Template-Joomla [skip ci] 2026-07-14 21:10:07 +00:00
jmiller 5494ed409c chore: sync standards-compliance.yml from Template-Joomla [skip ci] 2026-07-14 21:10:06 +00:00
jmiller f035a56f58 chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-07-14 21:10:05 +00:00
jmiller 59543a4d36 chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-07-14 21:10:04 +00:00
jmiller 70545956a7 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-07-14 21:10:03 +00:00
jmiller 0a784a3271 chore: sync pr-metadata-check.yml from Template-Joomla [skip ci] 2026-07-14 21:10:02 +00:00
jmiller 4abe87fba0 chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-07-14 21:10:01 +00:00
jmiller c36fcef138 chore: sync notify.yml from Template-Joomla [skip ci] 2026-07-14 21:09:59 +00:00
jmiller 5f3368e778 chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-07-14 21:09:58 +00:00
jmiller 71b2317b43 chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-07-14 21:09:57 +00:00
jmiller b523c91d00 chore: sync cleanup.yml from Template-Joomla [skip ci] 2026-07-14 21:09:56 +00:00
jmiller fdcc3226d9 chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-07-14 21:09:55 +00:00
jmiller f99db96451 chore: sync ci-issue-reporter.yml from Template-Joomla [skip ci] 2026-07-14 21:09:54 +00:00
jmiller 11cb107433 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-07-14 21:09:53 +00:00
jmiller e9e9788560 chore: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-07-14 21:09:52 +00:00
jmiller 5f62e3f8a7 chore: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-07-14 21:09:51 +00:00
jmiller ce248d659b chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-07-14 21:09:50 +00:00
jmiller 894972fd5b chore: sync auto-bump.yml from Template-Joomla [skip ci] 2026-07-14 21:09:49 +00:00
jmiller 631150a8ad chore: sync version-set.yml from Template-Joomla [skip ci] 2026-07-14 04:40:01 +00:00
jmiller 5111b348bf chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-07-14 04:39:59 +00:00
jmiller 77404ff3b0 chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-07-14 04:39:58 +00:00
jmiller 7cb937a752 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-07-14 04:39:57 +00:00
jmiller 8e877f1be2 chore: sync pr-metadata-check.yml from Template-Joomla [skip ci] 2026-07-14 04:39:56 +00:00
jmiller 49d7ddf79d chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-07-14 04:39:55 +00:00
jmiller 24268549b5 chore: sync notify.yml from Template-Joomla [skip ci] 2026-07-14 04:39:54 +00:00
jmiller e1b588c851 chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-07-14 04:39:53 +00:00
jmiller 5a54067487 chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-07-14 04:39:52 +00:00
jmiller 5f101b1a00 chore: sync ci-issue-reporter.yml from Template-Joomla [skip ci] 2026-07-14 04:39:51 +00:00
jmiller 11f1a7a293 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-07-14 04:39:50 +00:00
jmiller a6232e9800 chore: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-07-14 04:39:49 +00:00
jmiller e43a322ec0 chore: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-07-14 04:39:48 +00:00
jmiller 9fb0b150a6 chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-07-14 04:39:47 +00:00
jmiller 273fcfdb7d chore: sync auto-bump.yml from Template-Joomla [skip ci] 2026-07-14 04:39:45 +00:00
jmiller 940275ea05 Merge pull request 'chore(ci): remove template-only sync workflows from root' (#155) from chore/remove-template-sync-leaks into main
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Generic: Standards Compliance / Secret Scanning (push) Has been cancelled
Generic: Standards Compliance / License Header Validation (push) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (push) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (push) Has been cancelled
Generic: Standards Compliance / README Completeness Check (push) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (push) Has been cancelled
Generic: Standards Compliance / Line Length Check (push) Has been cancelled
Generic: Standards Compliance / File Naming Standards (push) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (push) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (push) Has been cancelled
Generic: Standards Compliance / File Size Limits (push) Has been cancelled
Generic: Standards Compliance / Binary File Detection (push) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (push) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (push) Has been cancelled
Generic: Standards Compliance / Accessibility Check (push) Has been cancelled
Generic: Standards Compliance / Performance Metrics (push) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Generic: Standards Compliance / Repository Health Check (push) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-13 20:44:20 +00:00
jmiller 9d82799277 chore(ci): remove template-only sync workflows from root
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 4s
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Lint & Validate (pull_request) Has been cancelled
Joomla: Extension CI / Release Readiness Check (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Generic: Standards Compliance / Secret Scanning (pull_request) Has been cancelled
Generic: Standards Compliance / License Header Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (pull_request) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (pull_request) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (pull_request) Has been cancelled
Generic: Standards Compliance / README Completeness Check (pull_request) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (pull_request) Has been cancelled
Generic: Standards Compliance / Line Length Check (pull_request) Has been cancelled
Generic: Standards Compliance / File Naming Standards (pull_request) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (pull_request) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (pull_request) Has been cancelled
Generic: Standards Compliance / File Size Limits (pull_request) Has been cancelled
Generic: Standards Compliance / Binary File Detection (pull_request) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (pull_request) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (pull_request) Has been cancelled
Generic: Standards Compliance / Accessibility Check (pull_request) Has been cancelled
Generic: Standards Compliance / Performance Metrics (pull_request) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Has been cancelled
Generic: Standards Compliance / Repository Health Check (pull_request) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (pull_request) Has been cancelled
sync-on-merge.yml and workflow-sync-trigger.yml are template-only (gated
if: Template-*), dead no-ops in this child repo, inherited at scaffold time.
sync-on-merge.yml is also broken. The real sync lives in the templates'
workflows/custom/.
2026-07-13 20:44:15 +00:00
jmiller ddba676a24 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 19:51:09 +00:00
jmiller ec8a8ec26f chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 16:21:22 +00:00
jmiller ae4fb4d80c chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 16:21:20 +00:00
jmiller d457929a54 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 16:21:18 +00:00
jmiller 36bc2319ea chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:57:56 +00:00
jmiller 35230c0f55 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-13 15:45:20 +00:00
jmiller 65ccb7afb6 chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-13 15:45:19 +00:00
jmiller 28cbadf1dc chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-13 15:45:18 +00:00
jmiller 8c188c19f0 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-13 15:45:17 +00:00
jmiller e9fec19358 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-13 15:45:16 +00:00
jmiller 3ce37abf2f chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-13 15:45:15 +00:00
jmiller ee6790bf49 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 15:45:14 +00:00
jmiller 446a31a680 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 15:45:13 +00:00
jmiller ab29ffc65f chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 15:45:12 +00:00
jmiller 5adfdd530b chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-13 15:45:11 +00:00
jmiller bbed991f7f chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:45:09 +00:00
jmiller a89322a3af chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-13 15:45:08 +00:00
jmiller 4eba8c5f98 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-13 15:45:07 +00:00
jmiller 85ad452af0 chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 15:45:06 +00:00
jmiller af4a3d055c chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:45:05 +00:00
jmiller 8504f7b8a2 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 15:45:03 +00:00
jmiller 0e92599e27 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-13 15:45:02 +00:00
jmiller 9a44e5ef20 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:28:32 +00:00
jmiller a21fa8e8af chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 10:11:37 +00:00
gitea-actions[bot] 7857cb5781 chore(release): build 02.29.00 [skip ci] 2026-07-13 09:50:56 +00:00
jmiller a537c0696e Merge pull request 'Release: dev -> main' (#153) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 2s
Generic: Project CI / Tests (pull_request) Failing after 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: PR Check / Secret Scan (pull_request) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 8s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 09:39:33 +00:00
mokogitea-actions[bot] 5a95d683ec Merge remote-tracking branch 'origin/main' into dev
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Generic: Project CI / Tests (pull_request) Failing after 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 3s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Successful in 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 9s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 13s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 15s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/workflows/issue-branch.yml
2026-07-13 04:38:54 -05:00
mokogitea-actions[bot] a7f92dfe9f chore(version): pre-release bump to 02.28.07-dev [skip ci] 2026-07-13 09:32:54 +00:00
mokogitea-actions[bot] 3901b4ce36 chore(version): pre-release bump to 02.28.06-dev [skip ci] 2026-07-13 09:32:36 +00:00
jmiller ea21903738 chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 11s
2026-07-13 09:31:36 +00:00
jmiller 74f31808d2 chore(sync): CI notify retry + JEXEC whole-file scan from Template-Generic
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-13 09:31:35 +00:00
jmiller 40faf56308 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 09:28:01 +00:00
jmiller ee22ee72ce chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 09:28:01 +00:00
jmiller bd2f955e9d chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 09:28:00 +00:00
mokogitea-actions[bot] 284a3f1c75 chore(version): pre-release bump to 02.28.05-dev [skip ci] 2026-07-13 02:24:44 +00:00
jmiller 9e9a3156ba Merge pull request 'chore: align to Joomla 6 / PHP 8.3' (#152) from fix/joomla6-php83 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 11s
2026-07-13 02:24:07 +00:00
mokogitea-actions[bot] 5c14ee8c50 chore(version): pre-release bump to 02.28.04-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
2026-07-13 01:48:54 +00:00
mokogitea-actions[bot] 69f9b9cd53 chore(version): pre-release bump to 02.28.03-dev [skip ci] 2026-07-13 01:48:19 +00:00
mokogitea-actions[bot] 8eaf9d396b chore(version): pre-release bump to 02.28.02-dev [skip ci] 2026-07-13 01:48:07 +00:00
jmiller e0fd8b606e chore: align .mokogitea/CLAUDE.md to Joomla 6 / PHP 8.3
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s
Generic: Project CI / Tests (pull_request) Failing after 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 8s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 01:43:10 +00:00
jmiller 1cfc2086d4 chore: align README.md to Joomla 6 / PHP 8.3
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s
2026-07-13 01:43:09 +00:00
jmiller c0b4890fb3 chore: align composer.json to Joomla 6 / PHP 8.3
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-13 01:43:08 +00:00
mokogitea-actions[bot] 43527c543f chore(version): pre-release bump to 02.28.01-dev [skip ci] 2026-07-13 00:27:40 +00:00
jmiller 560736d1e0 Merge pull request 'chore(sync): cascade main -> dev' (#148) from main into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 13s
2026-07-13 00:27:29 +00:00
gitea-actions[bot] a34f1a58b8 chore(release): build 02.28.00 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 00:27:25 +00:00
jmiller 2398f53d5c Merge pull request 'release: promote dev to main (social floating collapsible config + workflow cleanup)' (#150) from dev into main
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Failing after 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 4s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 2s
Sync Workflows to Repos / sync (push) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 00:24:38 +00:00
mokogitea-actions[bot] 3986cad1b7 chore(version): pre-release bump to 02.27.11-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 14s
2026-07-13 00:22:52 +00:00
jmiller 5dee7aec96 Merge pull request 'chore: sync main into dev (resolve 98-commit infra/doc divergence)' (#151) from chore/sync-main-to-dev into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Generic: Project CI / Tests (pull_request) Failing after 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 14s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Universal: PR Check / Require Docs Update (pull_request) Failing after 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-13 00:22:39 +00:00
mokogitea-actions[bot] bdce98c35b chore(version): pre-release bump to 02.27.10-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 00:22:37 +00:00
jmiller 82e04b1818 chore: sync main into dev (resolve infra/doc divergence)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Generic: Project CI / Tests (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 14s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 9s
Universal: PR Check / Validate PR (pull_request) Failing after 15s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/workflows/issue-branch.yml
#	CHANGELOG.md
#	SECURITY.md
#	source/html/layouts/joomla/module/card.php
#	source/html/layouts/mokoonyx/article-metadata.php
#	source/media/css/a11y-high-contrast.css
#	source/templateDetails.xml
2026-07-12 19:22:08 -05:00
gitea-actions[bot] 69dbb4fecb chore(version): pre-release bump to 02.27.09-dev [skip ci] 2026-07-13 00:12:22 +00:00
jmiller 782abe81db Merge pull request 'feat(social): collapsible + collapsed-by-default config for floating social bar' (#149) from feature/social-floating-collapsible-config into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 10s
2026-07-13 00:12:04 +00:00
gitea-actions[bot] 0fbff8671c chore(version): auto-bump patch 02.27.04-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-07-13 00:11:53 +00:00
jmiller abbd70b7f0 feat(social): add collapsible + collapsed-by-default config for floating social bar
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 3s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 8s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Adds two template params for the floating social icons sidebar:
- social_floating_collapsible: show/hide the collapse toggle handle
- social_floating_collapsed: start collapsed on first visit (default on)

The layout renders the initial collapsed state server-side (no flash),
gates the toggle button behind the collapsible param, and exposes the
admin default via data-default-collapsed. The JS honors a visitor's
saved localStorage preference first, falling back to the admin default.
2026-07-12 19:11:05 -05:00
jmiller cd40561639 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-06 17:03:32 +00:00
jmiller 6c238ccb94 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-06 03:49:05 +00:00
jmiller fdd1f32003 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-06 03:49:03 +00:00
jmiller 2133ce8384 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-06 03:49:02 +00:00
jmiller 2deb05feb4 Merge chore/normalize-docs: normalize doc/config refs
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 4s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 13s
Universal: PR Check / Validate PR (pull_request) Failing after 1m3s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-05 22:50:39 +00:00
jmiller bd38e57788 chore(docs): normalize refs — MokoStandards->org wiki, moko-platform->mokocli, residual MokoGitea/MokoSuite
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Non-workflow docs/config only (CLAUDE.md, README, branch-protection, ISSUE_TEMPLATE/config.yml,
ci-issue-reporter). Workflow files, manifest schema, and Makefile install paths left for the
forge-rebrand gate. Authored-by: Moko Consulting
2026-07-05 22:50:36 +00:00
jmiller de6aa22bdb Merge fix/rebrand-wiki-docs: normalize in-repo wiki branding
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 17s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 29s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 17s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m13s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-05 22:01:58 +00:00
jmiller 907b03e701 docs(wiki): normalize in-repo wiki branding (MokoGitea/mokocli/MokoStandards->org wiki/MokoSuite)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 30s
2026-07-05 22:01:56 +00:00
jmiller 18558f0386 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-05 21:31:37 +00:00
jmiller bab2a96148 chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-05 21:31:36 +00:00
jmiller 0baf843677 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-05 21:31:35 +00:00
jmiller 1f56b49f64 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 21:31:34 +00:00
jmiller b50aeefbdf chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 21:31:33 +00:00
jmiller e91844013f chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 21:31:32 +00:00
jmiller d448aca8a8 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-05 21:31:31 +00:00
jmiller 5103631d61 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-05 21:31:30 +00:00
jmiller 25e23af1f7 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 21:31:29 +00:00
jmiller d24c3f95ad chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:31:28 +00:00
jmiller cc74944480 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-05 21:31:27 +00:00
jmiller 5f4dc490ca chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-05 21:31:25 +00:00
jmiller 9171576664 chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-05 21:31:24 +00:00
jmiller 8fc1ada2f7 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:31:23 +00:00
jmiller 3a4d518a14 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-05 21:31:21 +00:00
jmiller 8af693ca8f chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 21:31:19 +00:00
jmiller d9544de034 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-05 21:08:05 +00:00
jmiller da1765f04d chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 21:08:03 +00:00
jmiller 5a52c5ed6f chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-05 20:39:34 +00:00
jmiller 4f1abee85f chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-05 20:39:32 +00:00
jmiller c19c43942a chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 20:39:30 +00:00
jmiller b410550fd8 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 20:39:27 +00:00
jmiller 6ad1a3d937 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 20:39:25 +00:00
jmiller 08c8d22189 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 20:39:21 +00:00
jmiller dc9d2955e8 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-05 20:34:22 +00:00
jmiller 635577e699 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 20:34:20 +00:00
jmiller 21bfd03693 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 20:34:19 +00:00
jmiller cef5f897d3 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 20:34:17 +00:00
jmiller 1e1d372ff0 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 20:34:15 +00:00
jmiller 80cd0ee5ab chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-05 20:34:13 +00:00
jmiller 0ac6dce06b chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-05 20:34:11 +00:00
jmiller 1820b98369 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-05 20:34:09 +00:00
jmiller a0f73280e4 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-05 20:34:08 +00:00
jmiller fc5bf68017 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 20:34:05 +00:00
jmiller b025362d92 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-05 00:04:12 +00:00
jmiller c0a762d37e chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-05 00:04:11 +00:00
jmiller ddaed01a38 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-04 23:25:55 +00:00
jmiller f66d80d61e chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-04 20:27:22 +00:00
jmiller 9ce4223bb3 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-04 20:21:59 +00:00
jmiller 61b42fdfde chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-04 20:21:56 +00:00
jmiller 1962b10dec chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-04 19:37:21 +00:00
jmiller 57fb62ffc3 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-04 19:37:18 +00:00
jmiller 1ee461f2c9 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-04 19:37:16 +00:00
jmiller b55c9ff833 chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-04 19:03:19 +00:00
jmiller 7ec42120fc chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-04 19:03:18 +00:00
jmiller e48c18449f chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-04 19:03:17 +00:00
jmiller c5d70581d5 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-04 19:03:16 +00:00
jmiller a3bb855a1c chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-04 19:03:15 +00:00
jmiller e56a44aae2 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-04 19:03:14 +00:00
jmiller a93a12b504 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-04 19:03:12 +00:00
jmiller 6c343b5c42 Merge pull request 'fix: remove orphaned deploy-manual workflow' (#147) from fix/remove-deploy-manual into main
fix: remove orphaned deploy-manual workflow [skip ci]
2026-06-30 18:33:27 +00:00
jmiller 5dadb43748 fix: remove orphaned deploy-manual workflow [skip ci] 2026-06-30 18:06:53 +00:00
jmiller a9094817ca chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-29 16:01:18 +00:00
gitea-actions[bot] e08fccd1d9 chore: promote changelog [Unreleased] → [02.27.06] 2026-06-29 16:00:47 +00:00
gitea-actions[bot] 892bdee751 chore(release): build 02.27.06 [skip ci] 2026-06-29 16:00:44 +00:00
jmiller 0d205bcc65 Merge pull request 'refactor(css): remove redundant .text-bg-* combined utility' (#146) from fix/text-bg-background-only into main 2026-06-29 16:00:30 +00:00
gitea-actions[bot] c54b164826 chore(version): pre-release bump to 02.27.06-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 17s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Successful in 3m18s
2026-06-29 15:55:28 +00:00
jmiller 168c091352 refactor(css): remove redundant .text-bg-* combined utility
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 13s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 36s
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
.text-bg-* both (a) had a misleading prefix and (b) forced a black/white
foreground that fought the theme. The theme already provides separate,
theme-aware utilities: .text-* (color: var(--*-rgb)) and .bg-*
(background-color: var(--*-rgb)). Remove .text-bg-* and compose
.text-{ctx} + .bg-{ctx} instead.

Authored-by: Moko Consulting
2026-06-29 10:55:09 -05:00
jmiller 0aa0569ba3 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-29 06:34:25 +00:00
jmiller c3d54b2c2e chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-27 20:43:33 +00:00
jmiller 4ce0c67880 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-27 05:31:51 +00:00
jmiller 96b4b42787 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-27 00:48:58 +00:00
jmiller 1a7a856a09 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 19:45:49 +00:00
jmiller 5f3f94f5f6 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 19:45:48 +00:00
jmiller d3dee12348 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-06-25 19:45:46 +00:00
jmiller 2c43418933 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-25 17:09:59 +00:00
jmiller f22f9dc440 chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-25 17:09:58 +00:00
jmiller f78290d8fa chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 17:09:58 +00:00
jmiller b0e7e84cd5 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-25 17:09:57 +00:00
jmiller 9b9b6aed89 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 17:09:57 +00:00
jmiller 15e8dc15f0 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-25 17:09:56 +00:00
jmiller 556d282550 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-25 17:09:55 +00:00
jmiller bace9d0205 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-25 17:09:55 +00:00
jmiller 69eda2d1c6 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-25 17:09:54 +00:00
jmiller 148aaecfa4 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-25 17:09:54 +00:00
jmiller bef3ce381c chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:50:02 +00:00
jmiller 1ca4549994 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-24 11:50:00 +00:00
jmiller 0cf1fed629 chore: remove security-audit.yml -- handled by MokoGitea 2026-06-23 18:27:06 +00:00
gitea-actions[bot] 1d428d0944 chore(version): pre-release bump to 02.27.03-dev [skip ci] 2026-06-23 18:12:19 +00:00
gitea-actions[bot] 888be7ebc0 chore(version): auto-bump patch 02.27.02-dev [skip ci] 2026-06-23 18:11:56 +00:00
jmiller a97b985101 chore: remove security-audit.yml -- handled by MokoGitea
Universal: Auto Version Bump / Version Bump (push) Successful in 17s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 26s
2026-06-23 18:04:59 +00:00
gitea-actions[bot] cba1423546 chore(version): pre-release bump to 02.27.01-dev [skip ci] 2026-06-23 18:04:35 +00:00
gitea-actions[bot] a17361879f chore(version): auto-bump patch 02.26.03-dev [skip ci] 2026-06-23 18:04:14 +00:00
jmiller c81df6609f chore: remove deploy-manual.yml -- no longer needed
Universal: Auto Version Bump / Version Bump (push) Successful in 26s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
2026-06-23 17:59:40 +00:00
jmiller bbe44cc31d chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-23 17:50:38 +00:00
jmiller b7ec2a7efa chore: remove deprecated .mokogitea/workflows/composer-publish.yml [skip ci] 2026-06-23 17:38:19 +00:00
jmiller d5bdb3871e chore: remove deprecated .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-06-23 17:38:15 +00:00
jmiller 0947250568 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-22 00:34:38 +00:00
jmiller 71b7b3307a chore: remove unused Makefile - builds handled by CI auto-release 2026-06-21 23:55:20 +00:00
jmiller 56b8d3b78c chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 22:02:24 +00:00
jmiller 440cf41c3e chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-21 16:05:14 +00:00
jmiller 575588a14f chore: sync composer-publish.yml from Template-Generic [skip ci] 2026-06-21 06:34:43 +00:00
jmiller 6f67f7af37 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-21 02:42:12 +00:00
gitea-actions[bot] 138ee2ac40 chore: promote changelog [Unreleased] → [02.27.00] 2026-06-21 02:42:04 +00:00
gitea-actions[bot] fe362d3b28 chore(release): build 02.27.00 [skip ci] 2026-06-21 02:42:00 +00:00
gitea-actions[bot] b1792627d7 chore(version): pre-release bump to 02.26.02-dev [skip ci] 2026-06-21 02:41:39 +00:00
jmiller ebf365dfb3 Merge pull request 'release: collapsible social bar, tooltips, URL validation, blog image fix' (#144) from dev into main 2026-06-21 02:41:21 +00:00
gitea-actions[bot] 254c06f1d6 chore(version): auto-bump patch 02.26.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 20s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 1m45s
2026-06-21 02:41:17 +00:00
Jonathan Miller a6f9352686 merge: resolve CHANGELOG conflict from main sync
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (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
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 10s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 13s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 19s
Universal: Auto Version Bump / Version Bump (push) Successful in 21s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 24s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 57s
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-20 21:40:34 -05:00
jmiller d21dc84564 Merge pull request 'fix: set blog item image object-fit to none' (#143) from fix/blog-image-object-fit into dev
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 13s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 35s
Universal: PR Check / Validate PR (pull_request) Successful in 35s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-21 02:35:03 +00:00
Jonathan Miller ca4730c12f fix: set blog item image object-fit to none
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 11s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-06-20 21:22:02 -05:00
jmiller 83b18da22b Merge pull request 'docs: add social icons to README features and update version' (#142) from docs/readme-social-icons into dev 2026-06-21 01:38:41 +00:00
Jonathan Miller 6c2857820e docs: add social icons to README features and update version
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 26s
Universal: PR Check / Validate PR (pull_request) Successful in 32s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 12s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-06-20 20:36:54 -05:00
jmiller 363971ffa8 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-21 01:27:30 +00:00
jmiller 5de3d6c25c Merge pull request 'feat: collapsible floating social bar, tooltips, and URL validation' (#141) from feature/social-floating-enhancements into dev 2026-06-21 01:25:24 +00:00
Jonathan Miller 7063b6d633 feat: add collapsible floating social bar, tooltips, and URL validation
Universal: Auto Version Bump / Version Bump (push) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 23s
Universal: PR Check / Validate PR (pull_request) Successful in 29s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 9s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Floating social bar now has a toggle button to collapse/expand,
  with state persisted in localStorage
- All social icon links show Bootstrap tooltips on hover with the
  platform name, placement-aware based on position
- Enhanced URL validation: server-side Joomla validate="url" on all
  social URL fields, stricter PHP regex with Joomla log warnings
  for skipped invalid URLs
- Updated CSS Variables tab docs for floating bar variables
2026-06-20 20:22:47 -05:00
jmiller 2b3bcbac9a chore: delete manifest.xml (no longer used) 2026-06-21 00:19:37 +00:00
jmiller c0aab513c0 chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-20 23:45:55 +00:00
jmiller 24e0a1af85 chore: sync gitleaks.yml from Template-Joomla [skip ci] 2026-06-20 23:45:54 +00:00
jmiller a05a93af76 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 23:45:53 +00:00
jmiller 4870336a5a chore: sync workflow-sync-trigger.yml from Template-Joomla [skip ci] 2026-06-20 23:07:40 +00:00
jmiller 5edcb6bd04 chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-20 23:07:40 +00:00
jmiller d1d2cb1f77 chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-20 23:07:39 +00:00
jmiller 388c9138c9 chore: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-06-20 23:07:38 +00:00
jmiller a49bcd7e65 chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 23:07:37 +00:00
jmiller 1592ab5efa chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-06-20 22:29:54 +00:00
jmiller d5513b9517 chore: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-20 22:29:54 +00:00
jmiller 36f4eeb122 chore: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-20 22:29:53 +00:00
jmiller 28d90a93a1 chore: sync cleanup.yml from Template-Joomla [skip ci] 2026-06-20 22:29:53 +00:00
jmiller 4dedece818 ci: sync security-audit.yml from Template-Joomla [skip ci] 2026-06-20 22:26:30 +00:00
jmiller 5e0d6720c6 ci: sync repo-health.yml from Template-Joomla [skip ci] 2026-06-20 22:26:02 +00:00
jmiller 2fd86c1f5b ci: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-20 22:25:53 +00:00
jmiller 07ec634dc0 ci: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-20 22:24:46 +00:00
jmiller d6d67f8913 ci: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-20 22:22:20 +00:00
jmiller f27ca6330e ci: sync cleanup.yml from Template-Joomla [skip ci] 2026-06-20 22:15:34 +00:00
jmiller 9b195d2a14 chore: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-20 21:35:16 +00:00
jmiller 748bf45646 chore: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 21:35:15 +00:00
jmiller 3b041f7008 ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 21:34:01 +00:00
jmiller 3ec4c57f42 ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 21:31:33 +00:00
jmiller 2170d8d83b ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 21:28:08 +00:00
jmiller 7692478e22 ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 21:26:57 +00:00
gitea-actions[bot] 3f1c5a317f chore: promote changelog [Unreleased] → [02.26.00] 2026-06-20 20:56:35 +00:00
gitea-actions[bot] 7ffee71dee chore(release): build 02.26.00 [skip ci] 2026-06-20 20:56:33 +00:00
101 changed files with 16322 additions and 2953 deletions
View File
@@ -1,6 +1,6 @@
# MokoOnyx # MokoOnyx
Joomla site template — successor to MokoCassiopeia. Base template for all WaaS client deployments. Joomla site template — successor to MokoCassiopeia. Base template for all MokoSuite client deployments.
## Quick Reference ## Quick Reference
@@ -8,7 +8,7 @@ Joomla site template — successor to MokoCassiopeia. Base template for all WaaS
|---|---| |---|---|
| **Element** | `tpl_mokoonyx` | | **Element** | `tpl_mokoonyx` |
| **Type** | Joomla site template | | **Type** | Joomla site template |
| **Language** | PHP 8.1+ / CSS / JS | | **Language** | PHP 8.3+ / CSS / JS |
| **Branch** | develop on `dev`, merge to `main` (protected) | | **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoOnyx Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/wiki) | | **Wiki** | [MokoOnyx Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/wiki) |
@@ -51,13 +51,13 @@ Client repos (`client-clarksvillefurs`, `client-optainfunding`, etc.) install `t
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js` - **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
- **Attribution**: `Authored-by: Moko Consulting` - **Attribution**: `Authored-by: Moko Consulting`
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`) - **Workflow directory**: `.mokogit/` (not `.gitea/` or `.github/`)
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper) - **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper)
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files - **Wiki**: documentation lives in the MokoGIT wiki, not `docs/` files
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home) - **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki)
## Coding Standards ## Coding Standards
- PHP 8.1+ minimum - PHP 8.3+ minimum
- SPDX license headers on all PHP files - SPDX license headers on all PHP files
- `defined('_JEXEC') or die;` on all PHP files - `defined('_JEXEC') or die;` on all PHP files
@@ -8,7 +8,7 @@ contact_links:
url: https://mokoconsulting.tech/ url: https://mokoconsulting.tech/
about: Get help or ask questions through our website about: Get help or ask questions through our website
- name: MokoStandards Documentation - name: MokoStandards Documentation
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform url: https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki
about: View our coding standards and best practices about: View our coding standards and best practices
- name: Report a Security Vulnerability - name: Report a Security Vulnerability
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
@@ -37,7 +37,7 @@ If you have ideas about how this could be implemented, share them here:
Add any other context, mockups, or screenshots about the feature request here. Add any other context, mockups, or screenshots about the feature request here.
## Relevant Standards ## Relevant Standards
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)? Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki)?
- [ ] Accessibility (WCAG 2.1 AA) - [ ] Accessibility (WCAG 2.1 AA)
- [ ] Localization (en_US/en_GB) - [ ] Localization (en_US/en_GB)
- [ ] Security best practices - [ ] Security best practices
@@ -35,7 +35,7 @@ Use this template only for:
<!-- Describe how this could be addressed --> <!-- Describe how this could be addressed -->
## Standards Reference ## Standards Reference
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)? Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki)?
- [ ] SPDX license identifiers - [ ] SPDX license identifiers
- [ ] Secret management - [ ] Secret management
- [ ] Dependency security - [ ] Dependency security
@@ -1,8 +1,8 @@
--- ---
name: WaaS Client Site Issue name: MokoSuite Client Site Issue
about: Report an issue with a WaaS client site (branding, deployment, media sync) about: Report an issue with a MokoSuite client site (branding, deployment, media sync)
title: '[WAAS] ' title: '[WAAS] '
labels: 'waas, client-site' labels: 'mokosuite, client-site'
assignees: '' assignees: ''
--- ---
@@ -18,7 +18,7 @@ assignees: ''
## Client Site ## Client Site
- **Client Org**: [e.g., ClarksvilleFurs] - **Client Org**: [e.g., ClarksvilleFurs]
- **Repo**: [e.g., client-waas-clarksvillefurs] - **Repo**: [e.g., client-mokosuite-clarksvillefurs]
- **Environment**: [Dev / Production] - **Environment**: [Dev / Production]
- **Site URL**: [dev or production URL omit if private] - **Site URL**: [dev or production URL omit if private]
@@ -52,7 +52,7 @@ Attach screenshots showing the issue (desktop and mobile if relevant).
## Template Details ## Template Details
- **Joomla Version**: [e.g., 5.x] - **Joomla Version**: [e.g., 5.x]
- **Template Name**: [e.g., clienttemplate] - **Template Name**: [e.g., clienttemplate]
- **MokoWaaS Plugin**: [Active / Inactive] - **MokoSuite Plugin**: [Active / Inactive]
- **MokoOnyx Admin**: [Active / Inactive] - **MokoOnyx Admin**: [Active / Inactive]
## CSS Custom Properties ## CSS Custom Properties
@@ -1,7 +1,7 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: moko-platform.Automation # INGROUP: moko-platform.Automation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/branch-protection.yml # PATH: /.gitea/workflows/branch-protection.yml
@@ -42,8 +42,8 @@ on:
default: '' default: ''
env: env:
GITEA_URL: https://git.mokoconsulting.tech GIT_URL: https://git.mokoconsulting.tech
GITEA_ORG: MokoConsulting GIT_ORG: MokoConsulting
permissions: permissions:
contents: read contents: read
@@ -59,10 +59,10 @@ jobs:
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: | run: |
API="${GITEA_URL}/api/v1" API="${GIT_URL}/api/v1"
# Platform/standards/infra repos to exclude # Platform/standards/infra repos to exclude
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting" EXCLUDE="git-org-config org-profile git-private .mokogit-private MokoStandards moko-platform MokoTesting"
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
if [ -n "${{ inputs.repos }}" ]; then if [ -n "${{ inputs.repos }}" ]; then
@@ -75,7 +75,7 @@ jobs:
while true; do while true; do
BATCH=$(curl -sS \ BATCH=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \ -H "Authorization: token ${GA_TOKEN}" \
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \ "${API}/orgs/${GIT_ORG}/repos?page=${PAGE}&limit=50" \
| jq -r '.[].name // empty') | jq -r '.[].name // empty')
[ -z "$BATCH" ] && break [ -z "$BATCH" ] && break
REPOS="$REPOS $BATCH" REPOS="$REPOS $BATCH"
@@ -108,7 +108,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
DRY_RUN: ${{ inputs.dry_run || 'false' }} DRY_RUN: ${{ inputs.dry_run || 'false' }}
run: | run: |
API="${GITEA_URL}/api/v1" API="${GIT_URL}/api/v1"
REPOS="${{ steps.repos.outputs.repos }}" REPOS="${{ steps.repos.outputs.repos }}"
SUCCESS=0 SUCCESS=0
@@ -215,7 +215,7 @@ jobs:
curl -sS -o /dev/null -w "" \ curl -sS -o /dev/null -w "" \
-X DELETE \ -X DELETE \
-H "Authorization: token ${GA_TOKEN}" \ -H "Authorization: token ${GA_TOKEN}" \
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true "${API}/repos/${GIT_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
# Create rule # Create rule
RESPONSE=$(curl -sS -w "\n%{http_code}" \ RESPONSE=$(curl -sS -w "\n%{http_code}" \
@@ -223,7 +223,7 @@ jobs:
-H "Authorization: token ${GA_TOKEN}" \ -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$RULE" \ -d "$RULE" \
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections") "${API}/repos/${GIT_ORG}/${REPO}/branch_protections")
HTTP=$(echo "$RESPONSE" | tail -1) HTTP=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d') BODY=$(echo "$RESPONSE" | sed '$d')
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/cleanup.yml # PATH: /.gitea/workflows/cleanup.yml
@@ -21,7 +21,7 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
cleanup: cleanup:
@@ -40,7 +40,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: | run: |
echo "=== Merged Branch Cleanup ===" echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${GIT_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API # List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
@@ -69,7 +69,7 @@ jobs:
GA_TOKEN: ${{ secrets.GA_TOKEN }} GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: | run: |
echo "=== Workflow Run Cleanup ===" echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${GIT_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs # Get old completed runs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Deploy # INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template # PATH: /templates/workflows/joomla/deploy-manual.yml.template
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Security # INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template # PATH: /templates/workflows/gitleaks.yml.template
@@ -38,7 +38,7 @@ permissions:
env: env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }} NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
jobs: jobs:
gitleaks: gitleaks:
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Notifications # INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/notify.yml # PATH: /.gitea/workflows/notify.yml
@@ -27,7 +27,7 @@ permissions:
env: env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }} NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
jobs: jobs:
notify: notify:
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.CI # INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/pr-check.yml # PATH: /.gitea/workflows/pr-check.yml
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Release # INGROUP: MokoStandards.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/pre-release.yml # PATH: /.gitea/workflows/pre-release.yml
@@ -29,9 +29,9 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs: jobs:
build: build:
@@ -103,8 +103,8 @@ jobs:
fi fi
# Commit version bump # Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "git-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "git-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A git add -A
git diff --cached --quiet || { git diff --cached --quiet || {
@@ -120,11 +120,11 @@ jobs:
if [ -z "$EXT_ELEMENT" ]; then if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in case "$EXT_ELEMENT" in
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; templatedetails|manifest) EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac esac
fi fi
else else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi fi
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip" ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
@@ -172,7 +172,7 @@ jobs:
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT" echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)" echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
- name: Create or replace Gitea release - name: Create or replace Git release
id: release id: release
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
@@ -182,7 +182,7 @@ jobs:
ZIP_NAME="${{ steps.meta.outputs.zip_name }}" ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}" EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
TOKEN="${{ secrets.GA_TOKEN }}" TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
BRANCH=$(git branch --show-current) BRANCH=$(git branch --show-current)
BODY="## ${VERSION} ($(date +%Y-%m-%d)) BODY="## ${VERSION} ($(date +%Y-%m-%d))
@@ -237,7 +237,7 @@ jobs:
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \ export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \ PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO" PY_GIT_ORG="$GIT_ORG" PY_GIT_REPO="$GIT_REPO"
python3 << 'PYEOF' python3 << 'PYEOF'
import re, os import re, os
@@ -247,9 +247,9 @@ jobs:
zip_name = os.environ["PY_ZIP_NAME"] zip_name = os.environ["PY_ZIP_NAME"]
tag = os.environ["PY_TAG"] tag = os.environ["PY_TAG"]
date = os.environ["PY_DATE"] date = os.environ["PY_DATE"]
gitea_org = os.environ["PY_GITEA_ORG"] git_org = os.environ["PY_GIT_ORG"]
gitea_repo = os.environ["PY_GITEA_REPO"] git_repo = os.environ["PY_GIT_REPO"]
download_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}" download_url = f"https://git.mokoconsulting.tech/{git_org}/{git_repo}/releases/download/{tag}/{zip_name}"
with open("updates.xml", "r") as f: with open("updates.xml", "r") as f:
content = f.read() content = f.read()
@@ -280,8 +280,8 @@ jobs:
# Commit and push to current branch # Commit and push to current branch
if ! git diff --quiet updates.xml 2>/dev/null; then if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "git-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "git-actions[bot]"
git add updates.xml git add updates.xml
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]" git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
git push origin HEAD 2>&1 || echo "WARNING: push failed" git push origin HEAD 2>&1 || echo "WARNING: push failed"
@@ -290,8 +290,8 @@ jobs:
- name: "Sync updates.xml to all branches" - name: "Sync updates.xml to all branches"
run: | run: |
CURRENT_BRANCH="${{ github.ref_name }}" CURRENT_BRANCH="${{ github.ref_name }}"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "git-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "git-actions[bot]"
# Sync updates.xml to main and dev (whichever isn't current) # Sync updates.xml to main and dev (whichever isn't current)
for BRANCH in main dev; do for BRANCH in main dev; do
@@ -312,7 +312,7 @@ jobs:
- name: "Delete lesser pre-release channels (cascade)" - name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.GA_TOKEN }}" TOKEN="${{ secrets.GA_TOKEN }}"
STABILITY="${{ steps.meta.outputs.stability }}" STABILITY="${{ steps.meta.outputs.stability }}"
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Git.Workflow
# INGROUP: MokoStandards.Security # INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/security-audit.yml # PATH: /.gitea/workflows/security-audit.yml
@@ -30,7 +30,7 @@ permissions:
env: env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }} NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
jobs: jobs:
audit: audit:
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml # PATH: /.mokogit/workflows/auto-bump.yml
# VERSION: 09.02.00 # VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) # BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -22,7 +22,7 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions: permissions:
contents: write contents: write
@@ -40,10 +40,10 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup mokocli tools - name: Setup MokoCLI tools
run: | run: |
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
@@ -52,7 +52,7 @@ jobs:
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else else
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \ "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
@@ -62,5 +62,5 @@ jobs:
run: | run: |
php ${MOKO_CLI}/version_auto_bump.php \ php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \ --path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGIT_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" --repo-url "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
@@ -3,33 +3,42 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from MokoGIT repo metadata (API)
# #
# +========================================================================+ # +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+ # +=======================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads MokoGIT repo metadata (joomla|dolibarr|generic) to branch logic. |
# | | # | |
# | Platform-specific: | # | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages | # | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset | # | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream | # | generic: README-only, no update stream |
# | | # | |
# +========================================================================+ # +=======================================================================+
name: "Universal: Build & Release" name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, synchronize, closed]
branches: branches:
- main - main
paths-ignore:
- '.mokogit/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
- '.gitignore'
- '.gitattributes'
- '.gitmessage'
- 'LICENSE'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
action: action:
@@ -43,32 +52,38 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions: permissions:
contents: write contents: write
jobs: jobs:
# ── PR Opened → Rename branch to RC and build RC release ───────────────────── # ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc: promote-rc:
name: Promote to RC name: Promote to RC
runs-on: release runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || !startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') (
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
)
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1 fetch-depth: 1
submodules: recursive
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
@@ -91,28 +106,28 @@ jobs:
run: | run: |
php ${MOKO_CLI}/branch_rename.php \ php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGIT_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ --api-base "${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}" \
--pr "${{ github.event.pull_request.number }}" --pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git - name: Checkout rc and configure git
run: | run: |
git fetch origin rc git fetch origin rc
git checkout rc git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release - name: Publish RC release
run: | run: |
php ${MOKO_CLI}/release_publish.php \ php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \ --path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --token "${{ secrets.MOKOGIT_TOKEN }}"
- name: Update RC release notes from CHANGELOG.md - name: Update RC release notes from CHANGELOG.md
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Extract [Unreleased] section from changelog # Extract [Unreleased] section from changelog
NOTES="" NOTES=""
@@ -149,26 +164,31 @@ jobs:
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ──────────────────── # ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release: release:
name: Build & Release Pipeline name: Build & Release Pipeline
runs-on: release runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >- if: >-
github.event.pull_request.merged == true || !startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') (
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
)
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
submodules: recursive
- name: Configure git for bot pushes - name: Configure git for bot pushes
run: | run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers - name: Check for merge conflict markers
run: | run: |
@@ -183,9 +203,9 @@ jobs:
fi fi
echo "No conflict markers found" echo "No conflict markers found"
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: | run: |
@@ -205,6 +225,12 @@ jobs:
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi fi
- name: "Detect platform"
id: platform
run: |
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
- name: "Determine version bump level" - name: "Determine version bump level"
id: bump id: bump
run: | run: |
@@ -226,12 +252,60 @@ jobs:
fi fi
php ${MOKO_CLI}/release_publish.php \ php ${MOKO_CLI}/release_publish.php \
--path . --stability stable ${BUMP_FLAG} --branch main \ --path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --token "${{ secrets.MOKOGIT_TOKEN }}"
- name: "Read published version"
id: version
run: |
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]]; then
echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
else
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi
echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver
if: |
steps.version.outputs.skip != 'true' &&
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}"
elif [ "$HTTP_CODE" = "409" ]; then
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
else
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog - name: Update release notes and promote changelog
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Get the stable release info (version and ID) # Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \ RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
@@ -299,10 +373,10 @@ jobs:
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_mirror.php \ php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \ --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \ --gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true --branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
@@ -324,36 +398,28 @@ jobs:
&& echo "main branch pushed to GitHub mirror" \ && echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed" || echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main" - name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc) # Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \ "${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found" && echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch # dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ # (cascade-dev.yml), which runs after this release completes.
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main" - name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}" BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD) MAIN_SHA=$(git rev-parse HEAD)
@@ -373,9 +439,9 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \ php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true --branch dev --path . 2>&1 || true
# -- Summary -------------------------------------------------------------- # -- Summary --------------------------------------------------------------
@@ -399,5 +465,5 @@ jobs:
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi fi
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Universal # INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml # PATH: /.mokogit/workflows/branch-cleanup.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge # BRIEF: Delete feature branches after PR merge
@@ -33,10 +33,11 @@ jobs:
run: | run: |
BRANCH="${{ github.event.pull_request.head.ref }}" BRANCH="${{ github.event.pull_request.head.ref }}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") # URL-encode the branch name's slashes (no PHP dependency on the runner)
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
"${API}/${ENCODED}" 2>/dev/null || true) "${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then if [ "$STATUS" = "204" ]; then
@@ -0,0 +1,65 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoCLI.Release
# BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11.
#
# +========================================================================+
# | CASCADE MAIN -> DEV |
# +========================================================================+
# | dev mirrors main and is reset on every release. |
# | delete+recreate cannot run against a protected branch, so this |
# | force-pushes main -> dev. The automation identity is force-push |
# | allowlisted on dev via branch-protection.yml. |
# | Runs AFTER the release workflow completes, so dev picks up the |
# | fully version-bumped + changelog-promoted main (not the pre-bump |
# | state). Force-push (not merge) => no version-file conflicts. |
# +========================================================================+
name: "Cascade Main -> Dev"
on:
workflow_run:
workflows: ["Universal: Build & Release"]
types: [completed]
workflow_dispatch:
concurrency:
group: cascade-dev-${{ github.repository }}
cancel-in-progress: true
permissions:
contents: write
jobs:
cascade:
name: Reset dev to main
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
steps:
- name: Force dev to main
env:
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git init -q sync && cd sync
git config user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config user.name "mokogit-actions[bot]"
git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git"
git fetch -q --depth=1 origin main
if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then
# dev exists -> force it to match main (dev is a mirror of main)
git push --force origin FETCH_HEAD:refs/heads/dev
echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY"
else
# dev missing (e.g. renamed to rc mid-cycle) -> recreate from main
git push origin FETCH_HEAD:refs/heads/dev
echo "dev created from main" >> "$GITHUB_STEP_SUMMARY"
fi
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.CI # INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.gitea/workflows/ci-generic.yml # PATH: /.mokogit/workflows/ci-generic.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js) # BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
@@ -32,6 +32,8 @@ jobs:
lint: lint:
name: Lint & Validate name: Lint & Validate
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Checkout - name: Checkout
@@ -130,6 +132,9 @@ jobs:
name: Tests name: Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: lint needs: lint
# Run only when lint succeeded; always() forces evaluation so a skipped
# lint (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.lint.result == 'success' }}
steps: steps:
- name: Checkout - name: Checkout
@@ -0,0 +1,68 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a MokoGIT issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
on:
workflow_call:
inputs:
gate:
description: "CI gate name (e.g. PR Validation, Repository Health)"
required: true
type: string
details:
description: "Human-readable failure description"
required: true
type: string
severity:
description: "error or warning"
required: false
type: string
default: "error"
workflow:
description: "Workflow name for the issue title"
required: false
type: string
default: ""
secrets:
MOKOGIT_TOKEN:
required: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
report:
name: "Report: ${{ inputs.gate }}"
runs-on: ubuntu-latest
steps:
- name: Clone MokoCLI
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
/tmp/mokocli/cli/ci_issue_reporter.sh \
--gate "${{ inputs.gate }}" \
--details "${{ inputs.details }}" \
--severity "${{ inputs.severity }}" \
--workflow "${{ inputs.workflow }}"
File diff suppressed because it is too large Load Diff
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoCLI.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.gitea/workflows/cleanup.yml # PATH: /.mokogit/workflows/cleanup.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
@@ -21,7 +21,7 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
cleanup: cleanup:
@@ -33,30 +33,30 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GA_TOKEN }} token: ${{ secrets.MOKOGIT_TOKEN }}
- name: Delete merged branches - name: Delete merged branches
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: | run: |
echo "=== Merged Branch Cleanup ===" echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API # List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name') "${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0 DELETED=0
for BRANCH in $BRANCHES; do for BRANCH in $BRANCHES; do
# Skip protected branches # Skip protected branches
case "$BRANCH" in case "$BRANCH" in
main|master|develop|release/*|hotfix/*) continue ;; main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
esac esac
# Check if branch is merged into main # Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}" echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true "${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
fi fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs - name: Clean old workflow runs
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: | run: |
echo "=== Workflow Run Cleanup ===" echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs # Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \ "${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0 DELETED=0
for RUN_ID in $RUNS; do for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
done done
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Security # INGROUP: MokoCLI.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/gitleaks.yml.template # PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
@@ -25,10 +25,6 @@
name: "Universal: Secret Scanning" name: "Universal: Secret Scanning"
on: on:
pull_request:
branches:
- main
- 'dev/**'
schedule: schedule:
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC - cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
workflow_dispatch: workflow_dispatch:
@@ -38,7 +34,7 @@ permissions:
env: env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }} NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
jobs: jobs:
gitleaks: gitleaks:
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: moko-platform.Automation # INGROUP: MokoCLI.Automation
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
@@ -19,7 +19,7 @@ permissions:
issues: write issues: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
create-branch: create-branch:
@@ -28,8 +28,8 @@ jobs:
steps: steps:
- name: Create branch and comment - name: Create branch and comment
run: | run: |
TOKEN="${{ secrets.GA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}" ISSUE_TITLE="${{ github.event.issue.title }}"
@@ -58,7 +58,7 @@ jobs:
echo "Created branch: ${BRANCH}" echo "Created branch: ${BRANCH}"
# Comment on issue with branch link # Comment on issue with branch link
REPO_URL="${GITEA_URL}/${{ github.repository }}" REPO_URL="${MOKOGIT_URL}/${{ github.repository }}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`" BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \ curl -sf -X POST \
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoStandards.Notifications # INGROUP: MokoCLI.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.gitea/workflows/notify.yml # PATH: /.mokogit/workflows/notify.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure # BRIEF: Push notifications via ntfy on release success or workflow failure
@@ -15,9 +15,9 @@ name: "Universal: Notifications"
on: on:
workflow_run: workflow_run:
workflows: workflows:
- "Joomla Build & Release" - "Universal: Build & Release"
- "Joomla Extension CI" - "Joomla: Extension CI"
- "Deploy" - "Generic: Project CI"
types: types:
- completed - completed
@@ -26,7 +26,7 @@ permissions:
env: env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }} NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
jobs: jobs:
notify: notify:
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.CI # INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template # PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge # BRIEF: PR gate — branch policy + code validation before merge
@@ -96,10 +96,38 @@ jobs:
echo "Branch policy: OK (${HEAD} → ${BASE})" echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Secret Scanning ──────────────────────────────────────────────────
gitleaks:
name: Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
- name: Scan PR commits for secrets
run: |
if gitleaks detect --source . --verbose \
--log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "::error::Potential secrets detected in PR commits"
exit 1
fi
# ── Code Validation ──────────────────────────────────────────────────── # ── Code Validation ────────────────────────────────────────────────────
validate: validate:
name: Validate PR name: Validate PR
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip on template repos (Template-*) — no real manifest/source/changelog to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Checkout - name: Checkout
@@ -121,11 +149,12 @@ jobs:
- name: Detect platform - name: Detect platform
id: platform id: platform
run: | run: |
# Read platform from XML manifest (<platform> tag) or plain text fallback # Platform comes from the MokoGIT metadata API (public GET); manifest.xml is no longer used.
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1) API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]') PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
[ -z "$PLATFORM" ] && PLATFORM="generic" [ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Detected platform: $PLATFORM"
- name: Setup PHP - name: Setup PHP
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
@@ -256,10 +285,10 @@ jobs:
for ELEMENT in name version description; do for ELEMENT in name version description; do
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
done done
# Block legacy raw/branch update server URLs on MokoGitea # Block legacy raw/branch update server URLs on MokoGIT
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogit\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" echo "::error::Manifest contains legacy raw/branch update server URL on MokoGIT. Use the MokoGIT Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS" echo "$RAW_URLS"
exit 1 exit 1
fi fi
@@ -466,43 +495,33 @@ jobs:
name: Build RC Package name: Build RC Package
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [branch-policy, validate] needs: [branch-policy, validate]
# Run only when both gates succeeded; always() forces evaluation so a skipped
# validate (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }}
steps: steps:
- name: Trigger RC pre-release - name: Trigger RC pre-release
env: env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }} BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: | run: |
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" curl -s -X POST "${MOKOGIT_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGIT_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ────────────────────────────────────────────────────── # ── Issue Reporter ──────────────────────────────────────────────────────
report-issues: report-issues:
name: Report Issues name: Report Issues
runs-on: ubuntu-latest
needs: [branch-policy, validate] needs: [branch-policy, validate]
if: >- if: >-
always() && always() &&
needs.validate.result == 'failure' needs.validate.result == 'failure'
uses: ./.mokogit/workflows/ci-issue-reporter.yml
steps: with:
- name: Checkout gate: "PR Validation"
uses: actions/checkout@v4 workflow: "PR Check"
with: severity: error
sparse-checkout: automation/ci-issue-reporter.sh details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
sparse-checkout-cone-mode: false secrets: inherit
- name: "File issue for PR validation failure"
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x automation/ci-issue-reporter.sh
./automation/ci-issue-reporter.sh \
--gate "PR Validation" \
--workflow "PR Check" \
--severity error \
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Validation # INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template # PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs # BRIEF: Validate MokoGIT metadata matches Joomla extension manifest on PRs
name: "Joomla: Metadata Validation" name: "Joomla: Metadata Validation"
@@ -20,22 +20,24 @@ permissions:
contents: read contents: read
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs: jobs:
validate-metadata: validate-metadata:
name: "Validate Joomla Metadata" name: "Validate Joomla Metadata"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they have no real extension manifest to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup mokocli tools - name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
@@ -55,14 +57,14 @@ jobs:
- name: Validate metadata against Joomla manifest - name: Validate metadata against Joomla manifest
env: env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: | run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \ php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \ --path . \
--token "${GITEA_TOKEN}" \ --token "${MOKOGIT_TOKEN}" \
--org "${GITEA_ORG}" \ --org "${GIT_ORG}" \
--repo "${GITEA_REPO}" \ --repo "${GIT_REPO}" \
--api-base "${GITEA_URL}/api/v1" \ --api-base "${MOKOGIT_URL}/api/v1" \
--ci --ci
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@@ -3,11 +3,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Release # INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release" name: "Universal: Pre-Release"
@@ -40,29 +40,38 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs: jobs:
build: build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >- if: >-
github.event_name == 'workflow_dispatch' || !startsWith(github.event.repository.name, 'Template-') &&
github.event_name == 'push' (
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
)
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGIT_TOKEN }}
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
submodules: recursive
- name: Setup mokocli tools - name: Update submodules to main
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup MokoCLI tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
# Use pre-installed /opt/mokocli if available (updated by cron every 6h) # Use pre-installed /opt/mokocli if available (updated by cron every 6h)
@@ -88,8 +97,20 @@ jobs:
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
fi
- name: Resolve metadata and bump version - name: Resolve metadata and bump version
id: meta id: meta
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
# Auto-detect stability from branch name on push, or use input on dispatch # Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then if [ "${{ github.event_name }}" = "push" ]; then
@@ -135,9 +156,9 @@ jobs:
fi fi
# Commit version bump # Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A git add -A
git diff --cached --quiet || { git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
@@ -147,12 +168,12 @@ jobs:
# Auto-detect element via manifest_element.php # Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \ php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \ --path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output --repo "${GIT_REPO}" --github-output
# Read back element outputs # Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
@@ -166,20 +187,22 @@ jobs:
- name: Create release - name: Create release
id: release id: release
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_create.php \ php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \ --path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease --repo "${GIT_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md - name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading) # Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then if [ -f "CHANGELOG.md" ]; then
@@ -190,7 +213,7 @@ jobs:
fi fi
# Update release body via API # Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then if [ -n "$RELEASE_ID" ]; then
@@ -202,7 +225,7 @@ jobs:
'${API_BASE}/releases/${RELEASE_ID}', '${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH', data=payload, method='PATCH',
headers={ headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}', 'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}) })
urllib.request.urlopen(req) urllib.request.urlopen(req)
@@ -212,23 +235,25 @@ jobs:
- name: Build package and upload - name: Build package and upload
id: package id: package
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_package.php \ php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \ --path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true --repo "${GIT_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server # updates.xml is generated dynamically by MokoGIT license server
# No need to build, commit, or sync updates.xml from workflows # No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)" - name: "Delete lesser pre-release channels (cascade)"
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \ php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \ --stability "${{ steps.meta.outputs.stability }}" \
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoPlatform.Universal # INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml # PATH: /.mokogit/workflows/rc-revert.yml
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge # BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -29,12 +29,20 @@ jobs:
steps: steps:
- name: Rename branch - name: Rename branch
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: | run: |
BRANCH="${{ github.event.pull_request.head.ref }}" set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
fi
SUFFIX="${BRANCH#rc/}" SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}" DEV_BRANCH="dev/${SUFFIX}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" API="${GIT_URL}/api/v1/repos/${REPO}/branches"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Create dev/ branch from rc/ branch # Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
@@ -42,25 +50,22 @@ jobs:
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \ -d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true) "${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})" echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
exit 1
fi fi
# Delete rc/ branch # Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true) "${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})" echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi fi
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
@@ -6,9 +6,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: MokoGIT.Workflow
# INGROUP: mokocli.Validation # INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template # PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
@@ -45,7 +45,7 @@ env:
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
# Repo health policy # Repo health policy
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogit/workflows/
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS: REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md REPO_DISALLOWED_FILES: TODO.md,todo.md
@@ -56,7 +56,7 @@ env:
# File / directory variables # File / directory variables
DOCS_INDEX: docs/docs-index.md DOCS_INDEX: docs/docs-index.md
SCRIPT_DIR: scripts SCRIPT_DIR: scripts
WORKFLOWS_DIR: .mokogitea/workflows WORKFLOWS_DIR: .mokogit/workflows
SHELLCHECK_PATTERN: '*.sh' SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -77,7 +77,7 @@ jobs:
- name: Check actor permission (admin only) - name: Check actor permission (admin only)
id: perm id: perm
env: env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }} ACTOR: ${{ github.actor }}
run: | run: |
@@ -88,14 +88,14 @@ jobs:
# Hardcoded authorized users — always allowed # Hardcoded authorized users — always allowed
case "$ACTOR" in case "$ACTOR" in
jmiller|gitea-actions[bot]) jmiller|mokogit-actions[bot])
ALLOWED=true ALLOWED=true
PERMISSION=admin PERMISSION=admin
METHOD="hardcoded allowlist" METHOD="hardcoded allowlist"
;; ;;
*) *)
# Detect platform and check permissions via API # Detect platform and check permissions via API
API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" API_BASE="${GITHUB_API_URL:-${GIT_API_URL:-https://api.github.com}}"
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
@@ -605,7 +605,7 @@ jobs:
printf '%s\n' '| Domain | Status | Notes |' printf '%s\n' '| Domain | Status | Notes |'
printf '%s\n' '|---|---|---|' printf '%s\n' '|---|---|---|'
printf '%s\n' '| Access control | OK | Admin-only execution gate |' printf '%s\n' '| Access control | OK | Admin-only execution gate |'
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |' printf '%s\n' '| Release policy | N/A | Releases handled by MokoGIT |'
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
@@ -671,42 +671,30 @@ jobs:
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
# Issue Reporter — file issues for failed gates # Issue Reporter — file issues for failed gates
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
report-issues: report-scripts:
name: "Report Issues" name: "Report: Scripts Governance"
runs-on: ubuntu-latest needs: [access_check, scripts_governance]
needs: [access_check, scripts_governance, repo_health]
if: >- if: >-
always() && always() &&
(needs.scripts_governance.result == 'failure' || needs.scripts_governance.result == 'failure'
needs.repo_health.result == 'failure') uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "Scripts Governance"
workflow: "Repo Health"
severity: error
details: "Scripts directory policy violations detected. Review required and allowed directories."
secrets: inherit
steps: report-health:
- name: Checkout name: "Report: Repository Health"
uses: actions/checkout@v4 needs: [access_check, repo_health]
with: if: >-
sparse-checkout: automation/ci-issue-reporter.sh always() &&
sparse-checkout-cone-mode: false needs.repo_health.result == 'failure'
uses: ./.mokogit/workflows/ci-issue-reporter.yml
- name: "File issues for failed gates" with:
env: gate: "Repository Health"
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} workflow: "Repo Health"
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} severity: error
run: | details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
chmod +x automation/ci-issue-reporter.sh secrets: inherit
REPORTER="./automation/ci-issue-reporter.sh"
WF="Repo Health"
report_gate() {
local gate="$1" result="$2" details="$3"
if [ "$result" = "failure" ]; then
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
fi
}
report_gate "Scripts Governance" \
"${{ needs.scripts_governance.result }}" \
"Scripts directory policy violations detected. Review required and allowed directories."
report_gate "Repository Health" \
"${{ needs.repo_health.result }}" \
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow.Template
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogit/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump"
on:
push:
branches:
- dev
- rc
- 'feature/**'
- 'patch/**'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
jobs:
bump:
name: Version Bump
runs-on: release
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1
- name: Setup MokoCLI tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
if [ -d "/opt/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGIT_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+469
View File
@@ -0,0 +1,469 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from MokoGit repo metadata (API)
#
# +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +=======================================================================+
# | |
# | Reads MokoGit repo metadata (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +=======================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, synchronize, closed]
branches:
- main
paths-ignore:
- '.mokogit/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
- '.gitignore'
- '.gitattributes'
- '.gitmessage'
- 'LICENSE'
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: false
type: choice
default: release
options:
- release
- promote-rc
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc:
name: Promote to RC
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
)
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 1
submodules: recursive
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
run: |
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGIT_TOKEN }}" \
--api-base "${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: |
git fetch origin rc
git checkout rc
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGIT_TOKEN }}"
- name: Update RC release notes from CHANGELOG.md
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
fi
[ -z "$NOTES" ] && NOTES="Release candidate"
# Find the RC release and update its body
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/release-candidate" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "RC release notes updated from CHANGELOG.md"
fi
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release:
name: Build & Release Pipeline
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
)
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGIT_TOKEN }}
fetch-depth: 0
submodules: recursive
- name: Configure git for bot pushes
run: |
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found — aborting release"
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: "Detect platform"
id: platform
run: |
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
- name: "Determine version bump level"
id: bump
run: |
# Fix/patch branches: version was already bumped by pre-release, just strip suffix
# Feature/dev branches: bump minor for the new stable release
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
case "$HEAD_REF" in
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
*) BUMP="minor" ;;
esac
echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
- name: "Publish stable release"
run: |
BUMP_FLAG=""
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
fi
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGIT_TOKEN }}"
- name: "Read published version"
id: version
run: |
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]]; then
echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
else
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi
echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver
if: |
steps.version.outputs.skip != 'true' &&
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}"
elif [ "$HTTP_CODE" = "409" ]; then
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
else
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
VERSION=$(python3 -c "
import json, sys, re
r = json.load(sys.stdin)
name = r.get('name', '')
m = re.search(r'(\d+\.\d+\.\d+)', name)
print(m.group(1) if m else '')
" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
fi
[ -z "$NOTES" ] && NOTES="Stable release"
# Update release body via API
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
python3 -c "
import sys
version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read()
old = '## [Unreleased]'
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
content = content.replace(old, new, 1)
open('CHANGELOG.md', 'w').write(content)
" "$VERSION" "$DATE"
git add CHANGELOG.md
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
git push origin main || true
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
# (cascade-dev.yml), which runs after this release completes.
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+60
View File
@@ -0,0 +1,60 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogit/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
name: "Branch Cleanup"
on:
pull_request:
types: [closed]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
cleanup:
name: Delete merged branch
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true &&
github.event.pull_request.head.ref != 'dev' &&
github.event.pull_request.head.ref != 'main'
steps:
- name: Delete source branch
# SECURITY: the PR head ref is attacker-controllable. Pass it (and the
# repo/token) through env so the Actions engine cannot splice it into the
# shell source, and validate it to a safe charset before use.
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
API_BASE: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
set -euo pipefail
if ! printf '%s' "${BRANCH}" | grep -Eq '^[A-Za-z0-9._/-]+$'; then
echo "::error::unsafe branch name; refusing to proceed"; exit 1
fi
API="${API_BASE}/api/v1/repos/${REPO}/branches"
# URL-encode the branch name's slashes (no PHP dependency on the runner)
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
elif [ "$STATUS" = "404" ]; then
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
else
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
fi
+65
View File
@@ -0,0 +1,65 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Release
# BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11.
#
# +========================================================================+
# | CASCADE MAIN -> DEV |
# +========================================================================+
# | dev mirrors main and is reset on every release. |
# | delete+recreate cannot run against a protected branch, so this |
# | force-pushes main -> dev. The automation identity is force-push |
# | allowlisted on dev via branch-protection.yml. |
# | Runs AFTER the release workflow completes, so dev picks up the |
# | fully version-bumped + changelog-promoted main (not the pre-bump |
# | state). Force-push (not merge) => no version-file conflicts. |
# +========================================================================+
name: "Cascade Main -> Dev"
on:
workflow_run:
workflows: ["Universal: Build & Release"]
types: [completed]
workflow_dispatch:
concurrency:
group: cascade-dev-${{ github.repository }}
cancel-in-progress: true
permissions:
contents: write
jobs:
cascade:
name: Reset dev to main
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
steps:
- name: Force dev to main
env:
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git init -q sync && cd sync
git config user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config user.name "mokogit-actions[bot]"
git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git"
git fetch -q --depth=1 origin main
if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then
# dev exists -> force it to match main (dev is a mirror of main)
git push --force origin FETCH_HEAD:refs/heads/dev
echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY"
else
# dev missing (e.g. renamed to rc mid-cycle) -> recreate from main
git push origin FETCH_HEAD:refs/heads/dev
echo "dev created from main" >> "$GITHUB_STEP_SUMMARY"
fi
+202
View File
@@ -0,0 +1,202 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogit/workflows/ci-generic.yml
# VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
name: "Generic: Project CI"
on:
pull_request:
branches:
- main
- dev
- dev/**
- rc/**
workflow_dispatch:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ── Lint & Validate ───────────────────────────────────────────────────
lint:
name: Lint & Validate
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
php -v
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install PHP dependencies
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
fi
- name: Install Node.js dependencies
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "package.json" ]; then
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
fi
- name: PHP syntax check
if: steps.detect.outputs.has_php == 'true'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
echo "::error file=${file}::PHP syntax error"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -eq 0 ]; then
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
else
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: TypeScript/JavaScript lint
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "node_modules/.bin/eslint" ]; then
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
echo "::warning::ESLint config found but eslint not installed"
else
echo "No ESLint configured — skipping"
fi
- name: TypeScript compile check
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
fi
- name: PHPStan static analysis
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
fi
# ── Tests ─────────────────────────────────────────────────────────────
test:
name: Tests
runs-on: ubuntu-latest
needs: lint
# Run only when lint succeeded; always() forces evaluation so a skipped
# lint (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.lint.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
- name: Run PHP tests
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "vendor/bin/phpunit" ]; then
vendor/bin/phpunit --testdox 2>&1
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
echo "::warning::PHPUnit config found but phpunit not installed"
else
echo "No PHPUnit configured — skipping"
fi
- name: Run Node.js tests
if: steps.detect.outputs.has_node == 'true'
run: |
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
npm test 2>&1
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "No test script in package.json — skipping"
fi
- name: Build check
run: |
if [ -f "Makefile" ]; then
make build 2>&1 || echo "::warning::Build failed or not configured"
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
npm run build 2>&1 || echo "::warning::Build failed"
fi
+68
View File
@@ -0,0 +1,68 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a MokoGit issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
on:
workflow_call:
inputs:
gate:
description: "CI gate name (e.g. PR Validation, Repository Health)"
required: true
type: string
details:
description: "Human-readable failure description"
required: true
type: string
severity:
description: "error or warning"
required: false
type: string
default: "error"
workflow:
description: "Workflow name for the issue title"
required: false
type: string
default: ""
secrets:
MOKOGIT_TOKEN:
required: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
report:
name: "Report: ${{ inputs.gate }}"
runs-on: ubuntu-latest
steps:
- name: Clone MokoCLI
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
/tmp/mokocli/cli/ci_issue_reporter.sh \
--gate "${{ inputs.gate }}" \
--details "${{ inputs.details }}" \
--severity "${{ inputs.severity }}" \
--workflow "${{ inputs.workflow }}"
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
name: "Universal: Repository Cleanup"
on:
schedule:
- cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
workflow_dispatch:
permissions:
contents: write
env:
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
cleanup:
name: Clean Merged Branches
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGIT_TOKEN }}
- name: Delete merged branches
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
for BRANCH in $BRANCHES; do
# Skip protected branches
case "$BRANCH" in
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
esac
# Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1))
fi
done
echo "Deleted ${DELETED} merged branch(es)"
- name: Clean old workflow runs
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0
for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
echo "Deleted ${DELETED} old workflow run(s)"
+92
View File
@@ -0,0 +1,92 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
# +========================================================================+
# | SECRET SCANNING |
# +========================================================================+
# | |
# | Scans commits for leaked secrets using Gitleaks. |
# | |
# | - PR scan: only new commits in the PR |
# | - Scheduled: full repo scan weekly |
# | - Alerts via ntfy on findings |
# | |
# +========================================================================+
name: "Universal: Secret Scanning"
on:
schedule:
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
jobs:
gitleaks:
name: Gitleaks Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
gitleaks version
- name: Scan for secrets
id: scan
run: |
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Scan only PR commits
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
else
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
fi
if gitleaks detect $ARGS 2>&1; then
echo "result=clean" >> "$GITHUB_OUTPUT"
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "result=found" >> "$GITHUB_OUTPUT"
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Notify on findings
if: failure() && steps.scan.outputs.result == 'found'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} — secrets detected in code" \
-H "Tags: rotating_light,key" \
-H "Priority: urgent" \
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+81
View File
@@ -0,0 +1,81 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGIT.Workflow
# INGROUP: MokoCLI.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
on:
issues:
types: [opened]
permissions:
contents: write
issues: write
env:
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
create-branch:
name: Create feature branch
runs-on: ubuntu-latest
steps:
- name: Create branch and comment
# SECURITY: never interpolate github.event.* into a run: script — the
# Actions engine splices the raw value into the shell source (command
# injection via a crafted issue title). Pass everything through env so
# the values arrive as ordinary shell variables that are not re-parsed.
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUM: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
TOKEN="${MOKOGIT_TOKEN}"
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
# Build slug from title: lowercase, replace non-alnum with dash, trim.
# printf (not echo) so a title beginning with "-" is not read as flags.
SLUG=$(printf '%s' "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
# Check dev branch exists
DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
-H "Authorization: token ${TOKEN}" \
"${API}/branches/dev" 2>/dev/null || echo "000")
if [ "${DEV_EXISTS}" != "200" ]; then
echo "No dev branch -- skipping"
exit 0
fi
# Create branch from dev
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/branches" \
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
if [ "${HTTP}" = "201" ]; then
echo "Created branch: ${BRANCH}"
# Comment on issue with branch link
REPO_URL="${MOKOGIT_URL}/${REPO}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${ISSUE_NUM}/comments" \
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
echo "Commented on issue #${ISSUE_NUM}"
else
echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
fi
+70
View File
@@ -0,0 +1,70 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
name: "Universal: Notifications"
on:
workflow_run:
workflows:
- "Universal: Build & Release"
- "Joomla: Extension CI"
- "Generic: Project CI"
types:
- completed
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
jobs:
notify:
name: Send Notification
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure'
steps:
- name: Notify on success (releases only)
if: >-
github.event.workflow_run.conclusion == 'success' &&
contains(github.event.workflow_run.name, 'Release')
run: |
REPO="${{ github.event.repository.name }}"
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
-H "Title: ${REPO} released" \
-H "Tags: white_check_mark,package" \
-H "Priority: default" \
-H "Click: ${URL}" \
-d "${WORKFLOW} completed successfully." \
"${NTFY_URL}/${NTFY_TOPIC}"
- name: Notify on failure
if: github.event.workflow_run.conclusion == 'failure'
run: |
REPO="${{ github.event.repository.name }}"
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
-H "Title: ${REPO} workflow failed" \
-H "Tags: x,warning" \
-H "Priority: high" \
-H "Click: ${URL}" \
-d "${WORKFLOW} failed. Check the run for details." \
"${NTFY_URL}/${NTFY_TOPIC}"
+527
View File
@@ -0,0 +1,527 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge
name: "Universal: PR Check"
on:
pull_request:
types: [opened, synchronize, reopened, edited]
permissions:
contents: read
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ── Branch Policy ──────────────────────────────────────────────────────
branch-policy:
name: Branch Policy
runs-on: ubuntu-latest
steps:
- name: Check branch merge target
run: |
HEAD="${{ github.head_ref }}"
BASE="${{ github.base_ref }}"
echo "PR: ${HEAD} → ${BASE}"
ALLOWED=true
REASON=""
case "$HEAD" in
feature/*|feat/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Feature branches must target 'dev', not '${BASE}'"
fi
;;
fix/*|bugfix/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Fix branches must target 'dev', not '${BASE}'"
fi
;;
patch/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
ALLOWED=false
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
fi
;;
hotfix/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
fi
;;
rc)
if [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="RC branch can only merge into 'main', not '${BASE}'"
fi
;;
dev)
if [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Dev branch can only merge into 'main', not '${BASE}'"
fi
;;
esac
if [ "$ALLOWED" = false ]; then
echo "::error::${REASON}"
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Secret Scanning ──────────────────────────────────────────────────
gitleaks:
name: Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
- name: Scan PR commits for secrets
run: |
if gitleaks detect --source . --verbose \
--log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "::error::Potential secrets detected in PR commits"
exit 1
fi
# ── Code Validation ────────────────────────────────────────────────────
validate:
name: Validate PR
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — no real manifest/source/changelog to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found in source files"
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Detect platform
id: platform
run: |
# Platform comes from the MokoGit metadata API (public GET); manifest.xml is no longer used.
API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Detected platform: $PLATFORM"
- name: Setup PHP
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
- name: PHP syntax check
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
echo "PHP lint: ${ERRORS} error(s)"
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
- name: Joomla JEXEC guard check
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
# Skip vendor, node_modules, and index.html stub files
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
# Check first 10 lines for JEXEC or JPATH guard
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
echo "::error file=${file}::Missing JEXEC guard: ${file}"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
if [ "$ERRORS" -gt 0 ]; then
echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "JEXEC guard: OK"
- name: Joomla directory listing protection
if: steps.platform.outputs.platform == 'joomla'
run: |
MISSING=0
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && exit 0
while IFS= read -r dir; do
if [ ! -f "${dir}/index.html" ]; then
echo "::warning::Missing index.html in ${dir} (directory listing protection)"
MISSING=$((MISSING + 1))
fi
done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
if [ "$MISSING" -gt 0 ]; then
echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
fi
echo "Directory protection: ${MISSING} missing (advisory)"
- name: Joomla script file and asset checks
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && exit 0
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check scriptfile exists if declared
SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
if [ -n "$SCRIPTFILE" ]; then
if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</scriptfile> but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
ERRORS=$((ERRORS + 1))
else
echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
fi
fi
# Require joomla.asset.json and validate it
ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ASSET_JSON" ]; then
echo "::error::joomla.asset.json not found — Joomla asset system is required"
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
echo "::error::joomla.asset.json is not valid JSON"
ERRORS=$((ERRORS + 1))
}
fi
echo "joomla.asset.json: valid"
fi
# Validate all XML files in src/ are well-formed
XML_ERRORS=0
if command -v php &> /dev/null; then
while IFS= read -r -d '' xmlfile; do
if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
XML_ERRORS=$((XML_ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
fi
if [ "$XML_ERRORS" -gt 0 ]; then
echo "::error::${XML_ERRORS} XML file(s) are malformed"
ERRORS=$((ERRORS + 1))
else
echo "XML well-formedness: OK"
fi
[ "$ERRORS" -gt 0 ] && exit 1
echo "Joomla asset checks: OK"
- name: Validate platform manifest
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
case "$PLATFORM" in
joomla)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla manifest found (WaaS site)"
exit 0
fi
echo "Manifest: ${MANIFEST}"
if command -v php &> /dev/null; then
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
fi
for ELEMENT in name version description; do
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
done
# Block legacy raw/branch update server URLs on MokoGit
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogit\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGit. Use the MokoGit Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
echo "Joomla manifest valid"
;;
dolibarr)
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
if [ -z "$MOD_FILE" ]; then
echo "::error::No mod*.class.php found"
exit 1
fi
echo "Dolibarr module: ${MOD_FILE}"
;;
*)
echo "Generic platform — no manifest validation"
;;
esac
- name: Check update stream format
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
case "$PLATFORM" in
joomla)
if [ -f "updates.xml" ]; then
if command -v php &> /dev/null; then
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
fi
echo "updates.xml valid"
fi
;;
dolibarr)
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
;;
esac
- name: Validate Joomla language files
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
WARNINGS=0
# Require both en-GB and en-US language directories
LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$LANG_ROOT" ]; then
echo "No language/ directory found — skipping"
exit 0
fi
if [ ! -d "$LANG_ROOT/en-GB" ]; then
echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
ERRORS=$((ERRORS + 1))
fi
if [ ! -d "$LANG_ROOT/en-US" ]; then
echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
ERRORS=$((ERRORS + 1))
fi
# Check that en-GB and en-US have matching .ini files
if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
[ ! -f "$GB_INI" ] && continue
US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
if [ ! -f "$US_INI" ]; then
echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
ERRORS=$((ERRORS + 1))
fi
done
for US_INI in "$LANG_ROOT/en-US"/*.ini; do
[ ! -f "$US_INI" ] && continue
GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
if [ ! -f "$GB_INI" ]; then
echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
ERRORS=$((ERRORS + 1))
fi
done
fi
# Find all .ini language files
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
if [ -z "$INI_FILES" ]; then
echo "No .ini language files found"
[ "$ERRORS" -gt 0 ] && exit 1
exit 0
fi
echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
for FILE in $INI_FILES; do
FNAME=$(basename "$FILE")
LINENUM=0
SEEN_KEYS=""
while IFS= read -r line || [ -n "$line" ]; do
LINENUM=$((LINENUM + 1))
# Skip empty lines and comments
[ -z "$line" ] && continue
echo "$line" | grep -qE '^\s*;' && continue
echo "$line" | grep -qE '^\s*$' && continue
# Must match KEY="VALUE" format
if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
ERRORS=$((ERRORS + 1))
continue
fi
# Extract key and check for duplicates
KEY=$(echo "$line" | sed 's/=.*//')
if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
ERRORS=$((ERRORS + 1))
fi
SEEN_KEYS="${SEEN_KEYS}
${KEY}"
done < "$FILE"
echo " ${FILE}: checked ${LINENUM} lines"
done
# Cross-check en-GB vs en-US key consistency
GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
for GB_FILE in "$GB_DIR"/*.ini; do
[ ! -f "$GB_FILE" ] && continue
FNAME=$(basename "$GB_FILE")
US_FILE="$US_DIR/$FNAME"
[ ! -f "$US_FILE" ] && continue
GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
# Keys in en-GB but not en-US
MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_US" ]; then
echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
echo "$MISSING_US" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
# Keys in en-US but not en-GB
MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_GB" ]; then
echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
done
fi
{
echo "### Language File Validation"
echo "| Metric | Count |"
echo "|---|---|"
echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
echo "| Errors | ${ERRORS} |"
echo "| Warnings | ${WARNINGS} |"
} >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Language validation failed with ${ERRORS} error(s)"
exit 1
fi
echo "Language files: OK (${WARNINGS} warning(s))"
- name: Check changelog has unreleased entry
run: |
if [ ! -f "CHANGELOG.md" ]; then
echo "::warning::No CHANGELOG.md found"
exit 0
fi
# Check for content under [Unreleased] section
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
echo "::error::CHANGELOG.md missing [Unreleased] section"
exit 1
fi
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
- name: Verify package source
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ ! -d "$SOURCE_DIR" ]; then
echo "::warning::No src/ or htdocs/ directory"
exit 0
fi
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
echo "Source: ${FILE_COUNT} files"
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
# ── Pre-Release RC Build ─────────────────────────────────────────────────
pre-release:
name: Build RC Package
runs-on: ubuntu-latest
needs: [branch-policy, validate]
# Run only when both gates succeeded; always() forces evaluation so a skipped
# validate (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }}
steps:
- name: Trigger RC pre-release
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
curl -s -X POST "${MOKOGIT_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGIT_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ──────────────────────────────────────────────────────
report-issues:
name: Report Issues
needs: [branch-policy, validate]
if: >-
always() &&
needs.validate.result == 'failure'
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "PR Validation"
workflow: "PR Check"
severity: error
details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
secrets: inherit
+73
View File
@@ -0,0 +1,73 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
# VERSION: 01.00.00
# BRIEF: Validate MokoGit metadata matches Joomla extension manifest on PRs
name: "Joomla: Metadata Validation"
on:
pull_request:
types: [opened, synchronize, reopened, converted_to_draft, ready_for_review]
permissions:
contents: read
env:
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
validate-metadata:
name: "Validate Joomla Metadata"
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they have no real extension manifest to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Validate metadata against Joomla manifest
env:
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \
--token "${MOKOGIT_TOKEN}" \
--org "${GIT_ORG}" \
--repo "${GIT_REPO}" \
--api-base "${MOKOGIT_URL}/api/v1" \
--ci
if [ $? -ne 0 ]; then
echo "::error::Joomla metadata mismatch — update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details."
exit 1
fi
+277
View File
@@ -0,0 +1,277 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
on:
push:
branches:
- dev
- 'fix/**'
- 'patch/**'
- 'hotfix/**'
- 'bugfix/**'
- 'chore/**'
- alpha
- beta
- rc
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
)
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGIT_TOKEN }}
ref: ${{ github.ref_name }}
submodules: recursive
- name: Update submodules to main
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
# Auto-detect and update platform if not set in manifest
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
fi
- name: Resolve metadata and bump version
id: meta
if: steps.eligibility.outputs.proceed == 'true'
run: |
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogit-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GIT_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
if: steps.eligibility.outputs.proceed == 'true'
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GIT_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
if: steps.eligibility.outputs.proceed == 'true'
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
--repo "${GIT_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGit license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true
run: |
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+71
View File
@@ -0,0 +1,71 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogit/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
name: "RC Revert"
on:
pull_request:
types: [closed]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
revert:
name: Rename rc/ back to dev/
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == false &&
startsWith(github.event.pull_request.head.ref, 'rc/')
steps:
- name: Rename branch
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
run: |
set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
fi
SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}"
API="${GIT_URL}/api/v1/repos/${REPO}/branches"
# Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
fi
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
+700
View File
@@ -0,0 +1,700 @@
# ============================================================================
# Copyright (C) 2025 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: MokoGit.Workflow
# INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================
name: "Generic: Repo Health"
defaults:
run:
shell: bash
on:
workflow_dispatch:
inputs:
profile:
description: 'Validation profile: all, scripts, or repo'
required: true
default: all
type: choice
options:
- all
- scripts
- repo
pull_request:
branches:
- main
permissions:
contents: read
env:
# Scripts governance policy
SCRIPTS_REQUIRED_DIRS:
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
# Repo health policy
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogit/workflows/
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md
# Extended checks toggles
EXTENDED_CHECKS: "true"
# File / directory variables
DOCS_INDEX: docs/docs-index.md
SCRIPT_DIR: scripts
WORKFLOWS_DIR: .mokogit/workflows
SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
access_check:
name: Access control
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
outputs:
allowed: ${{ steps.perm.outputs.allowed }}
permission: ${{ steps.perm.outputs.permission }}
steps:
- name: Check actor permission (admin only)
id: perm
env:
TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
ALLOWED=false
PERMISSION=unknown
METHOD=""
# Hardcoded authorized users — always allowed
case "$ACTOR" in
jmiller|mokogit-actions[bot])
ALLOWED=true
PERMISSION=admin
METHOD="hardcoded allowlist"
;;
*)
# Detect platform and check permissions via API
API_BASE="${GITHUB_API_URL:-${GIT_API_URL:-https://api.github.com}}"
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then
ALLOWED=true
fi
METHOD="collaborator API"
;;
esac
echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT"
echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT"
{
echo "## Access Authorization"
echo ""
echo "| Field | Value |"
echo "|-------|-------|"
echo "| **Actor** | \`${ACTOR}\` |"
echo "| **Repository** | \`${REPO}\` |"
echo "| **Permission** | \`${PERMISSION}\` |"
echo "| **Method** | ${METHOD} |"
echo "| **Authorized** | ${ALLOWED} |"
echo ""
if [ "$ALLOWED" = "true" ]; then
echo "${ACTOR} authorized (${METHOD})"
else
echo "${ACTOR} is NOT authorized. Requires admin or maintain role."
fi
} >> "${GITHUB_STEP_SUMMARY}"
- name: Deny execution when not permitted
if: ${{ steps.perm.outputs.allowed != 'true' }}
run: |
set -euo pipefail
printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}"
exit 1
scripts_governance:
name: Scripts governance
needs: access_check
if: ${{ needs.access_check.outputs.allowed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Scripts folder checks
env:
PROFILE_RAW: ${{ github.event.inputs.profile }}
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|scripts|repo) ;;
*)
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = 'repo' ]; then
{
printf '%s\n' '### Scripts governance'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes scripts governance'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
if [ ! -d "${SCRIPT_DIR}" ]; then
{
printf '%s\n' '### Scripts governance'
printf '%s\n' 'Status: OK (advisory)'
printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi
IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
missing_dirs=()
unapproved_dirs=()
for d in "${required_dirs[@]}"; do
req="${d%/}"
[ ! -d "${req}" ] && missing_dirs+=("${req}/")
done
while IFS= read -r d; do
allowed=false
for a in "${allowed_dirs[@]}"; do
a_norm="${a%/}"
[ "${d%/}" = "${a_norm}" ] && allowed=true
done
[ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/")
done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
{
printf '%s\n' '### Scripts governance'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Area | Status | Notes |'
printf '%s\n' '|---|---|---|'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
else
printf '%s\n' '| Required directories | OK | All required subfolders present |'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
else
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
fi
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
printf '\n'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' 'Missing required script directories:'
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
else
printf '%s\n' 'Missing required script directories: none.'
printf '\n'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' 'Unapproved script directories detected:'
for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
else
printf '%s\n' 'Unapproved script directories detected: none.'
printf '\n'
fi
printf '%s\n' 'Scripts governance completed in advisory mode.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
repo_health:
name: Repository health
needs: access_check
if: ${{ needs.access_check.outputs.allowed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Repository health checks
env:
PROFILE_RAW: ${{ github.event.inputs.profile }}
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|scripts|repo) ;;
*)
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = 'scripts' ]; then
{
printf '%s\n' '### Repository health'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes repository health'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}"
missing_required=()
missing_optional=()
# Source directory: src/ or htdocs/ (either is valid for extension repos)
SOURCE_DIR=""
if [ -d "src" ]; then
SOURCE_DIR="src"
elif [ -d "htdocs" ]; then
SOURCE_DIR="htdocs"
elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then
# Platform/tooling repos don't need src/
SOURCE_DIR=""
else
missing_required+=("src/ or htdocs/ (source directory required)")
fi
for item in "${required_artifacts[@]}"; do
if printf '%s' "${item}" | grep -q '/$'; then
d="${item%/}"
[ ! -d "${d}" ] && missing_required+=("${item}")
else
[ ! -f "${item}" ] && missing_required+=("${item}")
fi
done
for f in "${optional_files[@]}"; do
if printf '%s' "${f}" | grep -q '/$'; then
d="${f%/}"
[ ! -d "${d}" ] && missing_optional+=("${f}")
else
[ ! -f "${f}" ] && missing_optional+=("${f}")
fi
done
for d in "${disallowed_dirs[@]}"; do
d_norm="${d%/}"
[ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)")
done
for f in "${disallowed_files[@]}"; do
[ -f "${f}" ] && missing_required+=("${f} (disallowed)")
done
git fetch origin --prune
dev_paths=()
dev_branches=()
while IFS= read -r b; do
name="${b#origin/}"
if [ "${name}" = 'dev' ]; then
dev_branches+=("${name}")
else
dev_paths+=("${name}")
fi
done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//')
if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then
missing_required+=("dev or dev/* branch")
fi
content_warnings=()
if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
fi
if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then
content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)")
fi
if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
content_warnings+=("LICENSE does not look like a GPL text")
fi
if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then
content_warnings+=("README.md missing expected brand keyword")
fi
export PROFILE_RAW="${profile}"
export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")"
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}")
{
printf '%s\n' '### Repository health'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Metric | Value |'
printf '%s\n' '|---|---|'
printf '%s\n' "| Missing required | ${#missing_required[@]} |"
printf '%s\n' "| Missing optional | ${#missing_optional[@]} |"
printf '%s\n' "| Content warnings | ${#content_warnings[@]} |"
printf '\n'
printf '%s\n' '### Guardrails report (JSON)'
printf '%s\n' '```json'
printf '%s\n' "${report_json}"
printf '%s\n' '```'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_required[@]}" -gt 0 ]; then
{
printf '%s\n' '### Missing required repo artifacts'
for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
if [ "${#missing_optional[@]}" -gt 0 ]; then
{
printf '%s\n' '### Missing optional repo artifacts'
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
if [ "${#content_warnings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Repo content warnings'
for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
# -- Joomla-specific checks --
joomla_findings=()
MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)"
if [ -z "${MANIFEST}" ]; then
joomla_findings+=("Joomla XML manifest not found (no *.xml with <extension> tag)")
else
if ! grep -qP '<version>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <version> tag missing")
fi
if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then
joomla_findings+=("XML manifest: type attribute missing or invalid")
fi
if ! grep -qP '<name>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <name> tag missing")
fi
if ! grep -qP '<author>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <author> tag missing")
fi
if ! grep -qP '<namespace' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <namespace> missing (required for Joomla 5+)")
fi
fi
INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)"
if [ "${INI_COUNT}" -eq 0 ]; then
joomla_findings+=("No .ini language files found")
fi
if [ ! -f 'updates.xml' ]; then
joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
fi
if [ -n "${SOURCE_DIR}" ]; then
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
for dir in "${INDEX_DIRS[@]}"; do
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
fi
done
fi
if [ "${#joomla_findings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' '| Check | Status |'
printf '%s\n' '|---|---|'
for f in "${joomla_findings[@]}"; do
printf '%s\n' "| ${f} | Warning |"
done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
else
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' 'All Joomla-specific checks passed.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
extended_enabled="${EXTENDED_CHECKS:-true}"
extended_findings=()
if [ "${extended_enabled}" = 'true' ]; then
if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then
:
else
extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)")
fi
if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then
bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)"
if [ -n "${bad_refs}" ]; then
extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt")
{
printf '%s\n' '### Workflow pinning advisory'
printf '%s\n' 'Found uses: entries pinned to main/master:'
printf '%s\n' '```'
printf '%s\n' "${bad_refs}"
printf '%s\n' '```'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
if [ -f "${DOCS_INDEX}" ]; then
missing_links=""
while IFS= read -r docline; do
for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do
case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac
linkpath="${link%%#*}"
linkpath="${linkpath%%\?*}"
[ -z "$linkpath" ] && continue
if [ "${linkpath:0:1}" = "/" ]; then
testpath="${linkpath#/}"
else
testpath="$(dirname "${DOCS_INDEX}")/${linkpath}"
fi
[ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} "
done
done < "${DOCS_INDEX}"
if [ -n "${missing_links}" ]; then
extended_findings+=("docs/docs-index.md contains broken relative links")
{
printf '%s\n' '### Docs index link integrity'
printf '%s\n' 'Broken relative links:'
for bl in ${missing_links}; do
printf '%s\n' "- ${bl}"
done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
if [ -d "${SCRIPT_DIR}" ]; then
if ! command -v shellcheck >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y shellcheck >/dev/null
fi
sc_out=''
while IFS= read -r shf; do
[ -z "${shf}" ] && continue
out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)"
if [ -n "${out_one}" ]; then
sc_out="${sc_out}${out_one}\n"
fi
done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort)
if [ -n "${sc_out}" ]; then
extended_findings+=("ShellCheck warnings detected (advisory)")
sc_head="$(printf '%s' "${sc_out}" | head -n 200)"
{
printf '%s\n' '### ShellCheck (advisory)'
printf '%s\n' '```'
printf '%s\n' "${sc_head}"
printf '%s\n' '```'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
spdx_missing=()
IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}"
spdx_args=()
for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done
while IFS= read -r f; do
[ -z "${f}" ] && continue
if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then
spdx_missing+=("${f}")
fi
done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true)
if [ "${#spdx_missing[@]}" -gt 0 ]; then
extended_findings+=("SPDX header missing in some tracked files (advisory)")
{
printf '%s\n' '### SPDX header advisory'
printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):'
for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
stale_cutoff_days=180
stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)"
if [ -n "${stale_branches}" ]; then
extended_findings+=("Stale remote branches detected (advisory)")
{
printf '%s\n' '### Git hygiene advisory'
printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):"
while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
fi
{
printf '%s\n' '### Guardrails coverage matrix'
printf '%s\n' '| Domain | Status | Notes |'
printf '%s\n' '|---|---|---|'
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGit |'
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
if [ "${extended_enabled}" = 'true' ]; then
if [ "${#extended_findings[@]}" -gt 0 ]; then
printf '%s\n' '| Extended checks | Warning | See extended findings below |'
else
printf '%s\n' '| Extended checks | OK | No findings |'
fi
else
printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |'
fi
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Extended findings (advisory)'
for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
site-health:
name: Site Health
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Uptime check
if: env.URLS != ''
run: |
echo "$URLS" > /tmp/urls.txt
php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down"
rm -f /tmp/urls.txt
env:
URLS: ${{ vars.MONITORED_URLS }}
- name: SSL certificate check
if: env.DOMAINS != ''
run: |
echo "$DOMAINS" > /tmp/domains.txt
php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon"
rm -f /tmp/domains.txt
env:
DOMAINS: ${{ vars.MONITORED_DOMAINS }}
- name: Summary
if: always()
run: |
echo "### Site Health" >> $GITHUB_STEP_SUMMARY
echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY
# ═══════════════════════════════════════════════════════════════════════
# Issue Reporter — file issues for failed gates
# ═══════════════════════════════════════════════════════════════════════
report-scripts:
name: "Report: Scripts Governance"
needs: [access_check, scripts_governance]
if: >-
always() &&
needs.scripts_governance.result == 'failure'
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "Scripts Governance"
workflow: "Repo Health"
severity: error
details: "Scripts directory policy violations detected. Review required and allowed directories."
secrets: inherit
report-health:
name: "Report: Repository Health"
needs: [access_check, repo_health]
if: >-
always() &&
needs.repo_health.result == 'failure'
uses: ./.mokogit/workflows/ci-issue-reporter.yml
with:
gate: "Repository Health"
workflow: "Repo Health"
severity: error
details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
secrets: inherit
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGit.Workflow.Template
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogit/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Moko Platform Repository Manifest
See: https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home
-->
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity>
<name>MokoOnyx</name>
<display-name>Template - MokoOnyx</display-name>
<org>MokoConsulting</org>
<description>MokoOnyx - Joomla site template (successor to MokoCassiopeia)</description>
<version>02.25.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
<platform>joomla</platform>
<standards-version>04.07.00</standards-version>
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
<last-synced>2026-05-10T19:51:09+00:00</last-synced>
</governance>
<build>
<language>PHP</language>
<package-type>joomla</package-type>
<entry-point>src/</entry-point>
</build>
</moko-platform>
-10
View File
@@ -1,10 +0,0 @@
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
jobs:
noop:
runs-on: ubuntu-latest
steps:
- run: echo "Cascade disabled — auto-release handles dev recreation"
-903
View File
@@ -1,903 +0,0 @@
# 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: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
# VERSION: 04.06.00
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
name: "Joomla: Extension CI"
on:
pull_request:
branches:
- main
- 'dev/**'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
lint-and-validate:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: |
if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then
echo "moko-platform already available on runner — skipping clone"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it"
fi
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: PHP syntax check
run: |
ERRORS=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
FOUND=1
while IFS= read -r -d '' FILE; do
OUTPUT=$(php -l "$FILE" 2>&1)
if echo "$OUTPUT" | grep -q "Parse error"; then
echo "::error file=${FILE}::${OUTPUT}"
ERRORS=$((ERRORS + 1))
fi
done < <(find "$DIR" -name "*.php" -print0)
fi
done
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
fi
- name: XML manifest validation
run: |
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find the extension manifest (XML with <extension tag)
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Validate well-formed XML
php -r "
\$xml = @simplexml_load_file('$MANIFEST');
if (\$xml === false) {
echo 'INVALID';
exit(1);
}
echo 'VALID';
" > /tmp/xml_result 2>&1
XML_RESULT=$(cat /tmp/xml_result)
if [ "$XML_RESULT" != "VALID" ]; then
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
fi
# Check required tags: name, version, author
for TAG in name version author; do
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
fi
done
# Namespace is required for components/plugins but not packages
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
if ! grep -q "<namespace" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<namespace>\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<namespace>\`" >> $GITHUB_STEP_SUMMARY
fi
else
echo "Package extension — \`<namespace>\` not required." >> $GITHUB_STEP_SUMMARY
fi
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check language files referenced in manifest
run: |
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -n "$MANIFEST" ]; then
# Extract language file references from manifest
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
if [ -z "$LANG_FILES" ]; then
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
else
while IFS= read -r LANG_FILE; do
LANG_FILE=$(echo "$LANG_FILE" | xargs)
if [ -z "$LANG_FILE" ]; then
continue
fi
# Check in common locations
FOUND=0
for BASE in "." "src" "htdocs"; do
if [ -f "${BASE}/${LANG_FILE}" ]; then
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
fi
done <<< "$LANG_FILES"
fi
else
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check index.html files in directories
run: |
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
MISSING=0
CHECKED=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
while IFS= read -r -d '' SUBDIR; do
CHECKED=$((CHECKED + 1))
if [ ! -f "${SUBDIR}/index.html" ]; then
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$DIR" -type d -print0)
fi
done
if [ "${CHECKED}" -eq 0 ]; then
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
elif [ "${MISSING}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
fi
- name: Check config.xml and access.xml for components
run: |
echo "### Component Config & ACL Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find all component manifests (XML with type="component")
COMP_MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<extension[^>]*type="component"' {} ; 2>/dev/null || true)
if [ -z "$COMP_MANIFESTS" ]; then
echo "No component extensions found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $COMP_MANIFESTS; do
COMP_DIR=$(dirname "$MANIFEST")
COMP_NAME=$(basename "$COMP_DIR")
echo "Component: `${COMP_NAME}` (manifest: `${MANIFEST}`)" >> $GITHUB_STEP_SUMMARY
# Check access.xml exists
ACCESS_FILE=$(find "$COMP_DIR" -name "access.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ACCESS_FILE" ]; then
echo "- Missing `access.xml` — ACL permissions will not work." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$ACCESS_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `access.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
for ACTION in core.admin core.manage; do
if ! grep -q "name=\"${ACTION}\"" "$ACCESS_FILE" 2>/dev/null; then
echo "- `access.xml` missing required action: `${ACTION}`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
echo "- `access.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
# Check config.xml exists
CONFIG_FILE=$(find "$COMP_DIR" -name "config.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$CONFIG_FILE" ]; then
echo "- Missing `config.xml` — component Options page will be empty." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$CONFIG_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `config.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- `config.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} config/ACL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Component config & ACL check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SQL schema validation
run: |
echo "### SQL Schema Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find SQL files in source/htdocs
SQL_FILES=$(find . -name "*.sql" -path "*/sql/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$SQL_FILES" ]; then
echo "No SQL files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$SQL_FILES" | wc -l) SQL file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $SQL_FILES; do
# Basic syntax check: balanced parentheses, no empty files
SIZE=$(wc -c < "$FILE" | tr -d ' ')
if [ "$SIZE" -eq 0 ]; then
echo "- Empty SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
# Check for common SQL errors
if grep -qP '^\s*$' "$FILE" && [ "$SIZE" -lt 5 ]; then
echo "- Whitespace-only SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
echo "- \`${FILE}\`: ${SIZE} bytes" >> $GITHUB_STEP_SUMMARY
done
# Check update SQL files follow version numbering pattern
UPDATE_DIR=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$UPDATE_DIR" ]; then
BAD_NAMES=0
for UFILE in "$UPDATE_DIR"/*.sql; do
[ ! -f "$UFILE" ] && continue
BASENAME=$(basename "$UFILE" .sql)
if ! echo "$BASENAME" | grep -qP '^\d+\.\d+\.\d+'; then
echo "- Update file \`${UFILE}\` does not follow version naming (expected X.Y.Z.sql)" >> $GITHUB_STEP_SUMMARY
BAD_NAMES=$((BAD_NAMES + 1))
fi
done
if [ "$BAD_NAMES" -gt 0 ]; then
ERRORS=$((ERRORS + BAD_NAMES))
fi
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} SQL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**SQL schema validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Manifest file references check
run: |
echo "### Manifest File References" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
else
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check <filename> references
FILENAMES=$(grep -oP '<filename[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILENAMES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ] && [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <folder> references
FOLDERS=$(grep -oP '<folder[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FOLDERS; do
if [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing folder: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <file> references in package manifests (ZIP files won't exist in source)
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
FILES=$(grep -oP '<file[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing file: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} missing file reference(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Manifest file references check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Form XML validation
run: |
echo "### Form XML Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
FORM_FILES=$(find . -name "*.xml" -path "*/forms/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$FORM_FILES" ]; then
echo "No form XML files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$FORM_FILES" | wc -l) form file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $FORM_FILES; do
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$FILE') ?: exit(1);" 2>/dev/null; then
echo "- \`${FILE}\`: malformed XML" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
# Check for valid Joomla form structure
if ! grep -qE '<form|<field|<fieldset' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: no \`<form>\`, \`<field>\`, or \`<fieldset>\` elements found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} form XML issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Form XML validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Deprecated Joomla API check
continue-on-error: true
run: |
echo "### Deprecated Joomla API Check" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
# Joomla 3/4 deprecated patterns that break in Joomla 6
PATTERNS=(
'JFactory::'
'JText::'
'JHtml::'
'JRoute::'
'JUri::'
'JLog::'
'JTable::'
'JInput'
'CMSFactory::\$application'
'JApplicationCms'
)
for PATTERN in "${PATTERNS[@]}"; do
HITS=$(grep -rnl "$PATTERN" "$SRC_DIR" --include="*.php" 2>/dev/null || true)
if [ -n "$HITS" ]; then
COUNT=$(echo "$HITS" | wc -l)
echo "- \`${PATTERN}\` found in ${COUNT} file(s)" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + COUNT))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} deprecated API usage(s) found.** These will break in Joomla 6." >> $GITHUB_STEP_SUMMARY
else
echo "**No deprecated APIs found.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Template output escaping check
continue-on-error: true
run: |
echo "### Template Output Escaping" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
TMPL_FILES=$(find . -name "*.php" -path "*/tmpl/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$TMPL_FILES" ]; then
echo "No template files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$TMPL_FILES" | wc -l) template file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $TMPL_FILES; do
# Check for unescaped output: <?= $var ?> or echo $var without escape()
UNESCAPED=$(grep -nP '<\?=\s*\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$UNESCAPED" ]; then
HITS=$(echo "$UNESCAPED" | wc -l)
echo "- \`${FILE}\`: ${HITS} unescaped \`<?= \$var ?>\` output(s) — use \`<?= \$this->escape(\$var) ?>\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
# Check for echo without escaping in template context
RAW_ECHO=$(grep -nP '^\s*echo\s+\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$RAW_ECHO" ]; then
HITS=$(echo "$RAW_ECHO" | wc -l)
echo "- \`${FILE}\`: ${HITS} raw \`echo \$var\` — consider \`echo \$this->escape(\$var)\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} potential XSS risk(s) in templates.** Review unescaped output." >> $GITHUB_STEP_SUMMARY
else
echo "**All template output appears properly escaped.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Namespace consistency check
run: |
echo "### Namespace Consistency" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find component/plugin manifests with <namespace> tags
MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<namespace' {} \; 2>/dev/null || true)
if [ -z "$MANIFESTS" ]; then
echo "No manifests with \`<namespace>\` found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $MANIFESTS; do
NS_PATH=$(grep -oP '<namespace[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$NS_PATH" ] && continue
MANIFEST_DIR=$(dirname "$MANIFEST")
echo "Manifest: \`${MANIFEST}\` → namespace \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
# Check PHP files have matching namespace
while IFS= read -r -d '' PHP_FILE; do
FILE_NS=$(grep -oP '^\s*namespace\s+\K[^;]+' "$PHP_FILE" 2>/dev/null | head -1)
[ -z "$FILE_NS" ] && continue
# Namespace should start with the manifest namespace path
if ! echo "$FILE_NS" | grep -qF "${NS_PATH}"; then
echo "- \`${PHP_FILE}\`: namespace \`${FILE_NS}\` doesn't match manifest \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.php" -path "*/src/*" -not -path "./vendor/*" -print0 2>/dev/null)
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} namespace mismatch(es).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Namespace consistency check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SPDX license header check
continue-on-error: true
run: |
echo "### SPDX License Headers" >> $GITHUB_STEP_SUMMARY
MISSING=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
TOTAL=0
while IFS= read -r -d '' FILE; do
TOTAL=$((TOTAL + 1))
if ! head -10 "$FILE" | grep -qi "SPDX"; then
echo "- Missing SPDX header: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$SRC_DIR" -name "*.php" -not -path "./vendor/*" -print0)
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$MISSING" -gt 0 ]; then
echo "**${MISSING}/${TOTAL} PHP file(s) missing SPDX license header.**" >> $GITHUB_STEP_SUMMARY
else
echo "**All ${TOTAL} PHP files have SPDX headers.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Service provider check
run: |
echo "### Service Provider Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
PROVIDERS=$(find . -name "provider.php" -path "*/services/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$PROVIDERS" ]; then
echo "No service providers found — skipping." >> $GITHUB_STEP_SUMMARY
else
for FILE in $PROVIDERS; do
# Must return a ServiceProviderInterface
if ! grep -qP 'ServiceProviderInterface|ComponentInterface|MVCFactoryInterface|DispatcherInterface' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: does not reference ServiceProviderInterface or component interfaces" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid service provider" >> $GITHUB_STEP_SUMMARY
fi
# Must have return statement
if ! grep -qP '^\s*return\s+new\s+' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: missing \`return new ...\` statement" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} service provider issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Service provider check passed.**" >> $GITHUB_STEP_SUMMARY
fi
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate release readiness
run: |
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Extract version from README.md
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
if [ -z "$README_VERSION" ]; then
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Find the extension manifest
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check <version> matches README VERSION
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
if [ -z "$MANIFEST_VERSION" ]; then
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check extension type, element, client attributes
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ -z "$EXT_TYPE" ]; then
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
fi
# Element check (component/module/plugin name)
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_ELEMENT" -eq 0 ]; then
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Client attribute for site/admin modules and plugins
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_CLIENT" -eq 0 ]; then
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
fi
fi
# Check updates.xml exists
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
else
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Check CHANGELOG.md exists
if [ -f "CHANGELOG.md" ]; then
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
else
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ $ERRORS -gt 0 ]; then
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
fi
test:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
needs: lint-and-validate
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php }}
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: Run tests
run: |
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
else
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
fi
static-analysis:
name: PHPStan Analysis
runs-on: ubuntu-latest
needs: lint-and-validate
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --optimize-autoloader
fi
- name: Install PHPStan
run: |
if ! command -v vendor/bin/phpstan &> /dev/null; then
composer require --dev phpstan/phpstan --no-interaction 2>/dev/null || \
composer global require phpstan/phpstan --no-interaction
fi
- name: Run PHPStan
run: |
echo "### PHPStan Static Analysis" >> $GITHUB_STEP_SUMMARY
PHPSTAN="vendor/bin/phpstan"
if [ ! -f "$PHPSTAN" ]; then
PHPSTAN=$(composer global config bin-dir --absolute 2>/dev/null)/phpstan
fi
# Determine source directory
SRC_DIR=""
for DIR in src/ htdocs/ lib/; do
if [ -d "$DIR" ]; then
SRC_DIR="$DIR"
break
fi
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Use repo phpstan.neon if present, otherwise use baseline config
ARGS="analyse ${SRC_DIR} --memory-limit=512M --no-progress --error-format=table"
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
echo "Using project PHPStan config." >> $GITHUB_STEP_SUMMARY
else
ARGS="$ARGS --level=3"
echo "No phpstan.neon found — using level 3 (type inference)." >> $GITHUB_STEP_SUMMARY
fi
$PHPSTAN $ARGS 2>&1 | tee /tmp/phpstan-output.txt
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "**No errors found.**" >> $GITHUB_STEP_SUMMARY
else
ERRORS=$(grep -c "ERROR" /tmp/phpstan-output.txt 2>/dev/null || echo "some")
echo "**${ERRORS} error(s) found.** Review output above." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -30 /tmp/phpstan-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
pre-release:
name: Build RC Pre-Release
runs-on: ubuntu-latest
needs: [lint-and-validate, test]
if: github.event_name == 'pull_request'
steps:
- name: Trigger pre-release build
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
run: |
curl -s -X POST \
"${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
-82
View File
@@ -1,82 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/security-audit.yml
# VERSION: 01.00.00
# BRIEF: Dependency vulnerability scanning for composer and npm packages
name: "Universal: Security Audit"
on:
schedule:
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
pull_request:
branches:
- main
paths:
- 'composer.json'
- 'composer.lock'
- 'package.json'
- 'package-lock.json'
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
jobs:
audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Composer audit
if: hashFiles('composer.lock') != ''
run: |
echo "=== Composer Security Audit ==="
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
fi
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "::warning::Composer vulnerabilities found"
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
else
echo "No known vulnerabilities in composer dependencies"
fi
- name: NPM audit
if: hashFiles('package-lock.json') != ''
run: |
echo "=== NPM Security Audit ==="
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
echo "No known vulnerabilities in npm dependencies"
else
echo "::warning::NPM vulnerabilities found"
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
fi
- name: Notify on vulnerabilities
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} has vulnerable dependencies" \
-H "Tags: lock,warning" \
-H "Priority: high" \
-d "Security audit found vulnerabilities. Review dependency updates." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
@@ -1,73 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoPlatform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokoplatform
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokoplatform.git" /tmp/mokoplatform
- name: Install dependencies
run: |
cd /tmp/mokoplatform
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokoplatform/cli/workflow_sync.php ${ARGS}
+7 -10
View File
@@ -8,22 +8,19 @@
DEFGROUP: Joomla.Template.Site DEFGROUP: Joomla.Template.Site
INGROUP: MokoOnyx.Documentation INGROUP: MokoOnyx.Documentation
PATH: ./CHANGELOG.md PATH: ./CHANGELOG.md
VERSION: 02.25.00 VERSION: 02.29.00
BRIEF: Changelog file documenting version history of MokoOnyx BRIEF: Changelog file documenting version history of MokoOnyx
--> -->
# Changelog — MokoOnyx (VERSION: 02.25.00) # Changelog — MokoOnyx (VERSION: 02.29.00)
## [Unreleased] ## [Unreleased]
## [02.25.00] --- 2026-06-20 ## [02.29.00] --- 2026-07-13
## [02.25.00] --- 2026-06-20 ## [02.28.00] --- 2026-07-13
## [02.24.00] --- 2026-06-20 ## [02.27.06] --- 2026-06-29
## [02.24.00] --- 2026-06-20 ## [02.27.06] --- 2026-06-29
## [02.23.02] --- 2026-06-20 ## [02.27.00] --- 2026-06-21
## [02.23.02] --- 2026-06-20
+2 -2
View File
@@ -34,7 +34,7 @@ feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge
7. **Merging to main** triggers the stable release pipeline: 7. **Merging to main** triggers the stable release pipeline:
- Minor version bump (e.g., `02.09.xx` → `02.10.00`) - Minor version bump (e.g., `02.09.xx` → `02.10.00`)
- Stability suffix stripped (clean version) - Stability suffix stripped (clean version)
- Gitea release created with ZIP/tar.gz packages - MokoGIT release created with ZIP/tar.gz packages
- `updates.xml` updated (Joomla extensions) - `updates.xml` updated (Joomla extensions)
- `dev` branch recreated from `main` - `dev` branch recreated from `main`
@@ -119,7 +119,7 @@ This ensures Joomla sites on ANY stability channel see the update (Joomla only s
The version tools update all files containing version stamps: The version tools update all files containing version stamps:
- `.mokogitea/manifest.xml` (canonical source) - `.mokogit/manifest.xml` (canonical source)
- Joomla XML manifests (`<version>` tag) - Joomla XML manifests (`<version>` tag)
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern) - `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
- `package.json`, `pyproject.toml` - `package.json`, `pyproject.toml`
-213
View File
@@ -1,213 +0,0 @@
# Makefile for MokoOnyx Joomla Template
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Build and validation powered by MokoStandards Enterprise API
# Install: composer install
# ==============================================================================
# CONFIGURATION
# ==============================================================================
EXTENSION_NAME := mokoonyx
EXTENSION_TYPE := template
EXTENSION_VERSION := $(shell grep -oP 'VERSION:\s*\K[0-9.]+' README.md 2>/dev/null || echo "0.0.0")
SRC_DIR := src
BUILD_DIR := build
DIST_DIR := dist
PHP := php
COMPOSER := composer
MOKO := vendor/bin/moko
# Colors
COLOR_RESET := \033[0m
COLOR_GREEN := \033[32m
COLOR_YELLOW := \033[33m
COLOR_BLUE := \033[34m
COLOR_RED := \033[31m
# ==============================================================================
# TARGETS
# ==============================================================================
.PHONY: help
help: ## Show this help message
@echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)"
@echo "$(COLOR_BLUE)║ MokoOnyx Template Build ║$(COLOR_RESET)"
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
@echo ""
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
@echo "Powered by: MokoStandards Enterprise API"
@echo ""
@echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
@echo ""
# ── Dependencies ──────────────────────────────────────────────────────────────
.PHONY: install-deps
install-deps: ## Install Composer dependencies (includes MokoStandards API)
@echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)"
@$(COMPOSER) install
@echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
.PHONY: update-deps
update-deps: ## Update Composer dependencies
@echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)"
@$(COMPOSER) update
@echo "$(COLOR_GREEN)✓ Dependencies updated$(COLOR_RESET)"
# ── Validation (MokoStandards API) ────────────────────────────────────────────
.PHONY: check-moko
check-moko:
@if [ ! -f "$(MOKO)" ]; then \
echo "$(COLOR_RED)✗ MokoStandards CLI not found. Run: make install-deps$(COLOR_RESET)"; \
exit 1; \
fi
.PHONY: lint
lint: check-moko ## PHP syntax check via MokoStandards
@echo "$(COLOR_BLUE)Running PHP syntax check...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:syntax -- --path .
@echo "$(COLOR_GREEN)✓ PHP syntax OK$(COLOR_RESET)"
.PHONY: check-joomla
check-joomla: check-moko ## Validate Joomla manifest via MokoStandards
@echo "$(COLOR_BLUE)Validating Joomla manifest...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:joomla -- --path .
@echo "$(COLOR_GREEN)✓ Joomla manifest valid$(COLOR_RESET)"
.PHONY: check-version
check-version: check-moko ## Verify version consistency across files
@echo "$(COLOR_BLUE)Checking version consistency...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:version -- --path .
@echo "$(COLOR_GREEN)✓ Versions consistent$(COLOR_RESET)"
.PHONY: check-headers
check-headers: check-moko ## Check license headers on source files
@echo "$(COLOR_BLUE)Checking license headers...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:headers -- --path .
@echo "$(COLOR_GREEN)✓ Headers OK$(COLOR_RESET)"
.PHONY: check-secrets
check-secrets: check-moko ## Scan for leaked credentials
@echo "$(COLOR_BLUE)Scanning for secrets...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:secrets -- --path .
@echo "$(COLOR_GREEN)✓ No secrets found$(COLOR_RESET)"
.PHONY: check-xml
check-xml: check-moko ## Validate XML files are well-formed
@echo "$(COLOR_BLUE)Checking XML files...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:xml -- --path .
@echo "$(COLOR_GREEN)✓ XML well-formed$(COLOR_RESET)"
.PHONY: validate
validate: lint check-joomla check-version check-xml check-headers check-secrets ## Run all MokoStandards validation checks
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
.PHONY: health
health: check-moko ## Full repository health check via MokoStandards
@echo "$(COLOR_BLUE)Running full health check...$(COLOR_RESET)"
@$(PHP) $(MOKO) health -- --path .
# ── Build ─────────────────────────────────────────────────────────────────────
.PHONY: clean
clean: ## Clean build artifacts
@echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)"
@rm -rf $(BUILD_DIR) $(DIST_DIR)
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
MOKO_PLATFORM ?= $(or $(wildcard ../moko-platform),$(wildcard $(HOME)/moko-platform),$(wildcard /opt/moko-platform))
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
.PHONY: minify
minify: ## Minify CSS/JS assets (requires terser + clean-css)
@echo "$(COLOR_BLUE)Minifying assets...$(COLOR_RESET)"
@if [ -f "$(MINIFY_SCRIPT)" ]; then \
node "$(MINIFY_SCRIPT)" $(SRC_DIR); \
elif [ -f "scripts/minify.js" ]; then \
node scripts/minify.js; \
else \
echo "$(COLOR_YELLOW)⚠ No minify script found$(COLOR_RESET)"; \
fi
.PHONY: build
build: clean minify ## Build template installable ZIP from src/
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)...$(COLOR_RESET)"
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
@cd $(BUILD_DIR)/package && \
if command -v zip >/dev/null 2>&1; then \
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" .; \
elif command -v pwsh >/dev/null 2>&1; then \
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
elif command -v powershell >/dev/null 2>&1; then \
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
else \
echo "$(COLOR_RED)✗ No zip tool found (zip, pwsh, powershell)$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: build-beta
build-beta: clean ## Build beta release ZIP
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)-beta...$(COLOR_RESET)"
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
@cd $(BUILD_DIR)/package && \
if command -v zip >/dev/null 2>&1; then \
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip" .; \
elif command -v pwsh >/dev/null 2>&1; then \
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
elif command -v powershell >/dev/null 2>&1; then \
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
else \
echo "$(COLOR_RED)✗ No zip tool found$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip$(COLOR_RESET)"
.PHONY: checksum
checksum: ## Generate SHA-256 checksums for dist packages
@echo "$(COLOR_BLUE)Generating checksums...$(COLOR_RESET)"
@for f in $(DIST_DIR)/*.zip; do \
sha256sum "$$f" | tee "$${f}.sha256"; \
done
@echo "$(COLOR_GREEN)✓ Checksums generated$(COLOR_RESET)"
# ── Release ───────────────────────────────────────────────────────────────────
.PHONY: release
release: validate build checksum ## Full release pipeline (validate + build + checksum)
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_BLUE)Next steps:$(COLOR_RESET)"
@echo " 1. Tag: git tag $(EXTENSION_VERSION)"
@echo " 2. Push: git push origin --tags"
@echo " 3. Create Gitea release and attach $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip"
@echo ""
# ── Info ──────────────────────────────────────────────────────────────────────
.PHONY: version
version: ## Display version and extension info
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
@echo " Name: $(EXTENSION_NAME)"
@echo " Type: $(EXTENSION_TYPE)"
@echo " Version: $(EXTENSION_VERSION)"
.PHONY: security-check
security-check: ## Run Composer security audit
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
@$(COMPOSER) audit
@echo "$(COLOR_GREEN)✓ Security check complete$(COLOR_RESET)"
.PHONY: all
all: install-deps validate build checksum ## Full pipeline: deps → validate → build → checksum
@echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)"
.DEFAULT_GOAL := help
+12 -10
View File
@@ -12,12 +12,12 @@ A modern, lightweight Joomla site template built on Cassiopeia with Font Awesome
| | | | | |
|---|---| |---|---|
| **Type** | Joomla Site Template | | **Type** | Joomla Site Template |
| **Version** | 02.07.00 | | **Version** | 02.22.00 |
| **Joomla** | 5.x / 6.x | | **Joomla** | 6.x+ |
| **PHP** | 8.1+ | | **PHP** | 8.3+ |
| **License** | GPL-3.0-or-later | | **License** | GPL-3.0-or-later |
| **Replaces** | MokoCassiopeia (auto-migrates on install) | | **Replaces** | MokoCassiopeia (auto-migrates on install) |
| **Repository** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) (primary) | | **Repository** | [MokoGIT](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) (primary) |
--- ---
@@ -39,6 +39,7 @@ On install, MokoOnyx automatically migrates settings, content references, and cu
| **Table of Contents** | Automatic TOC generation for long articles | | **Table of Contents** | Automatic TOC generation for long articles |
| **GTM / GA4** | Google Tag Manager and Analytics integration with smart visitor detection (login status, user group, page type) | | **GTM / GA4** | Google Tag Manager and Analytics integration with smart visitor detection (login status, user group, page type) |
| **Template Overrides** | Overrides for all core Joomla modules, Community Builder, and DPCalendar | | **Template Overrides** | Overrides for all core Joomla modules, Community Builder, and DPCalendar |
| **Social Icons** | Configurable social media links in topbar, footer, or collapsible floating sidebar with tooltips and URL validation |
| **Cassiopeia Base** | Minimal core overrides for maximum Joomla upgrade compatibility | | **Cassiopeia Base** | Minimal core overrides for maximum Joomla upgrade compatibility |
--- ---
@@ -52,8 +53,8 @@ On install, MokoOnyx automatically migrates settings, content references, and cu
### Requirements ### Requirements
- Joomla 5.x or 6.x - Joomla 6.x or higher
- PHP 8.1 or higher - PHP 8.3 or higher
--- ---
@@ -79,6 +80,7 @@ Key parameters include:
- **Google Tag Manager**: GTM container ID - **Google Tag Manager**: GTM container ID
- **GA4**: Measurement ID and tracking options - **GA4**: Measurement ID and tracking options
- **Table of Contents**: Auto-generate TOC for articles with heading threshold - **Table of Contents**: Auto-generate TOC for articles with heading threshold
- **Social Icons**: Display positions, icon style, and platform URLs
--- ---
@@ -86,14 +88,14 @@ Key parameters include:
| Repo | Purpose | | Repo | Purpose |
|------|---------| |------|---------|
| [Template-Client-WaaS](https://git.mokoconsulting.tech/MokoConsulting/Template-Client-WaaS/wiki) | Client site template (extends MokoOnyx) | | [Template-Client-MokoSuite](https://git.mokoconsulting.tech/MokoConsulting/Template-Client-MokoSuite/wiki) | Client site template (extends MokoOnyx) |
| [MokoWaaS](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki) | WaaS system plugin | | [MokoSuite](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki) | MokoSuite system plugin |
| [joomla-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp/wiki) | Joomla Web Services API MCP | | [joomla-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp/wiki) | Joomla Web Services API MCP |
| [deploy-mcp](https://git.mokoconsulting.tech/MokoConsulting/deploy-mcp/wiki) | Git-based deployment MCP | | [deploy-mcp](https://git.mokoconsulting.tech/MokoConsulting/deploy-mcp/wiki) | Git-based deployment MCP |
--- ---
> **[MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)** -- central standards hub for all Moko Consulting projects. > **[MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki)** -- central standards hub for all Moko Consulting projects.
--- ---
@@ -115,4 +117,4 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
--- ---
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogit/wiki)*
+1 -1
View File
@@ -10,7 +10,7 @@
INGROUP: MokoOnyx.Governance INGROUP: MokoOnyx.Governance
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
FILE: SECURITY.md FILE: SECURITY.md
VERSION: 02.25.00 VERSION: 02.29.00
BRIEF: Security policy and vulnerability reporting process for MokoOnyx. BRIEF: Security policy and vulnerability reporting process for MokoOnyx.
PATH: /SECURITY.md PATH: /SECURITY.md
NOTE: This policy is process oriented and does not replace secure engineering practices. NOTE: This policy is process oriented and does not replace secure engineering practices.
+1 -1
View File
@@ -11,7 +11,7 @@
], ],
"require": { "require": {
"ext-zip": "*", "ext-zip": "*",
"php": ">=8.1" "php": ">=8.3"
}, },
"require-dev": { "require-dev": {
"mokoconsulting-tech/enterprise": "^4.0" "mokoconsulting-tech/enterprise": "^4.0"
+1 -1
View File
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx * INGROUP: MokoOnyx
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /html/layouts/joomla/module/card.php * PATH: /html/layouts/joomla/module/card.php
* VERSION: 02.25.00 * VERSION: 02.29.00
* BRIEF: Custom card module chrome — renders module titles for all modules * BRIEF: Custom card module chrome — renders module titles for all modules
*/ */
@@ -11,7 +11,7 @@
* INGROUP: MokoOnyx.Layouts * INGROUP: MokoOnyx.Layouts
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /src/html/layouts/mokoonyx/article-metadata.php * PATH: /src/html/layouts/mokoonyx/article-metadata.php
* VERSION: 02.25.00 * VERSION: 02.29.00
* BRIEF: Article metadata footer layout -- renders jcfields grouped by field group * BRIEF: Article metadata footer layout -- renders jcfields grouped by field group
*/ */
+42 -7
View File
@@ -52,13 +52,22 @@ $platforms = [
$active = []; $active = [];
foreach ($platforms as $key => [$iconClass, $langKey]) { foreach ($platforms as $key => [$iconClass, $langKey]) {
$url = trim((string) $params->get('social_' . $key . '_url', '')); $url = trim((string) $params->get('social_' . $key . '_url', ''));
if ($url !== '' && preg_match('#^(https?://|mailto:|/)#i', $url)) { if ($url === '') {
$active[] = [ continue;
'url' => $url,
'iconClass' => $iconClass,
'label' => Text::_($langKey),
];
} }
if (!preg_match('#^(https?://[^\s<>"]+|mailto:[^\s<>"]+|/[^\s<>"]*)$#i', $url)) {
\Joomla\CMS\Log\Log::add(
'MokoOnyx social: skipped invalid URL for "' . $key . '": ' . $url,
\Joomla\CMS\Log\Log::WARNING,
'template'
);
continue;
}
$active[] = [
'url' => $url,
'iconClass' => $iconClass,
'label' => Text::_($langKey),
];
} }
if (empty($active)) { if (empty($active)) {
@@ -75,6 +84,10 @@ $floatingPos = in_array($params->get('social_floating_pos'), ['left', 'right'],
$colorMode = in_array($params->get('social_icon_color'), ['theme', 'brand', 'black', 'white'], true) $colorMode = in_array($params->get('social_icon_color'), ['theme', 'brand', 'black', 'white'], true)
? $params->get('social_icon_color') : 'theme'; ? $params->get('social_icon_color') : 'theme';
// Floating collapse behaviour (only meaningful for the floating position)
$collapsible = (bool) $params->get('social_floating_collapsible', 1);
$collapsedDefault = $collapsible && (bool) $params->get('social_floating_collapsed', 1);
$listClass = 'moko-social-icons'; $listClass = 'moko-social-icons';
$listClass .= ' moko-social-icons--' . htmlspecialchars($style, ENT_QUOTES, 'UTF-8'); $listClass .= ' moko-social-icons--' . htmlspecialchars($style, ENT_QUOTES, 'UTF-8');
$listClass .= ' moko-social-icons--' . htmlspecialchars($size, ENT_QUOTES, 'UTF-8'); $listClass .= ' moko-social-icons--' . htmlspecialchars($size, ENT_QUOTES, 'UTF-8');
@@ -84,6 +97,12 @@ if ($position === 'floating') {
$listClass .= ' moko-social-icons--floating-' . $floatingPos; $listClass .= ' moko-social-icons--floating-' . $floatingPos;
} }
?> ?>
<?php if ($position === 'floating') : ?>
<div class="moko-social-floating-wrap moko-social-floating-wrap--<?php echo htmlspecialchars($floatingPos, ENT_QUOTES, 'UTF-8'); ?><?php echo $collapsedDefault ? ' is-collapsed' : ''; ?>"
id="mokoSocialFloating"
data-collapsible="<?php echo $collapsible ? '1' : '0'; ?>"
data-default-collapsed="<?php echo $collapsedDefault ? '1' : '0'; ?>">
<?php endif; ?>
<nav class="<?php echo $listClass; ?>" aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_NAV_LABEL'); ?>"> <nav class="<?php echo $listClass; ?>" aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_NAV_LABEL'); ?>">
<ul> <ul>
<?php foreach ($active as $item) : ?> <?php foreach ($active as $item) : ?>
@@ -91,10 +110,26 @@ if ($position === 'floating') {
<a href="<?php echo htmlspecialchars($item['url'], ENT_QUOTES, 'UTF-8'); ?>" <a href="<?php echo htmlspecialchars($item['url'], ENT_QUOTES, 'UTF-8'); ?>"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>"> aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>"
data-bs-toggle="tooltip"
data-bs-placement="<?php echo $position === 'floating' ? ($floatingPos === 'left' ? 'right' : 'left') : 'top'; ?>"
title="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>">
<span class="<?php echo htmlspecialchars($item['iconClass'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true"></span> <span class="<?php echo htmlspecialchars($item['iconClass'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true"></span>
</a> </a>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</nav> </nav>
<?php if ($position === 'floating' && $collapsible) : ?>
<button type="button"
class="moko-social-floating-toggle"
id="mokoSocialFloatingToggle"
aria-controls="mokoSocialFloating"
aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE'); ?>"
aria-expanded="<?php echo $collapsedDefault ? 'false' : 'true'; ?>">
<span class="fa-solid fa-chevron-<?php echo $floatingPos === 'left' ? 'left' : 'right'; ?>" aria-hidden="true"></span>
</button>
<?php endif; ?>
<?php if ($position === 'floating') : ?>
</div>
<?php endif; ?>
+6 -1
View File
@@ -286,6 +286,11 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on." TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on."
TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left" TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right" TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right"
TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_LABEL="Collapsible"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_DESC="Show a toggle handle so visitors can collapse and expand the floating social bar. Their choice is remembered on their device."
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_LABEL="Collapsed by Default"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_DESC="Start the floating social bar collapsed on a visitor's first visit. Visitors who have already set their own preference keep it."
TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Colour" TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Colour"
TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the colour scheme for social icons." TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the colour scheme for social icons."
TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)" TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)"
@@ -321,7 +326,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us"
; ===== CSS Variables tab (social) ===== ; ===== CSS Variables tab (social) =====
TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons" TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)" TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
; ===== Misc ===== ; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:" MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
+1 -1
View File
@@ -28,4 +28,4 @@ TPL_MOKOONYX_POSITION_TOP_B="Top-b"
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar" TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left" TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right" TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
TPL_MOKOONYX_XML_DESCRIPTION="<p><img src=\"https://img.shields.io/gitea/v/release/MokoConsulting/MokoOnyx?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version\" alt=\"Version\" /> <img src=\"https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white\" alt=\"License\" /> <img src=\"https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white\" alt=\"Joomla\" /> <img src=\"https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white\" alt=\"PHP\" /></p> <h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>" TPL_MOKOONYX_XML_DESCRIPTION="<p><img src=\"https://img.shields.io/git/v/release/MokoConsulting/MokoOnyx?git_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=git&logoColor=white&label=version\" alt=\"Version\" /> <img src=\"https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white\" alt=\"License\" /> <img src=\"https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white\" alt=\"Joomla\" /> <img src=\"https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white\" alt=\"PHP\" /></p> <h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"
+6 -1
View File
@@ -286,6 +286,11 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on." TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on."
TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left" TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left"
TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right" TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right"
TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_LABEL="Collapsible"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_DESC="Show a toggle handle so visitors can collapse and expand the floating social bar. Their choice is remembered on their device."
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_LABEL="Collapsed by Default"
TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_DESC="Start the floating social bar collapsed on a visitor's first visit. Visitors who have already set their own preference keep it."
TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Color" TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Color"
TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the color scheme for social icons." TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the color scheme for social icons."
TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)" TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)"
@@ -321,7 +326,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us"
; ===== CSS Variables tab (social) ===== ; ===== CSS Variables tab (social) =====
TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons" TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons"
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)" TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
; ===== Misc ===== ; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:" MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
+1 -1
View File
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx.Accessibility * INGROUP: MokoOnyx.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: ./media/css/a11y-high-contrast.css * PATH: ./media/css/a11y-high-contrast.css
* VERSION: 02.25.00 * VERSION: 02.29.00
* BRIEF: High-contrast stylesheet for accessibility toolbar * BRIEF: High-contrast stylesheet for accessibility toolbar
*/ */
+67 -51
View File
@@ -8046,46 +8046,6 @@ fieldset:disabled .btn {
content: ""; content: "";
} }
.text-bg-primary {
color: var(--body-color, #22262a);
background-color: var(--primary, #010156);
}
.text-bg-secondary {
color: var(--body-color, #22262a) ;
background-color: RGBA(109, 117, 126, var(--bg-opacity, 1)) ;
}
.text-bg-success {
color: var(--body-color, #22262a) ;
background-color: RGBA(68, 131, 68, var(--bg-opacity, 1)) ;
}
.text-bg-info {
color: var(--body-color, #22262a) ;
background-color: RGBA(48, 99, 141, var(--bg-opacity, 1)) ;
}
.text-bg-warning {
color: var(--body-color, #22262a) ;
background-color: RGBA(173, 98, 0, var(--bg-opacity, 1)) ;
}
.text-bg-danger {
color: var(--body-color, #22262a) ;
background-color: RGBA(165, 31, 24, var(--bg-opacity, 1)) ;
}
.text-bg-light {
color: hsl(0, 0%, 0%) ;
background-color: RGBA(249, 250, 251, var(--bg-opacity, 1)) ;
}
.text-bg-dark {
color: var(--body-color, #22262a) ;
background-color: RGBA(53, 59, 65, var(--bg-opacity, 1)) ;
}
.link-primary { .link-primary {
color: var(--link-primary-color, hsl(240, 98%, 50%)); color: var(--link-primary-color, hsl(240, 98%, 50%));
@@ -23522,7 +23482,7 @@ font-size: 0.8125rem;
.blog-item .item-image img { .blog-item .item-image img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: none;
} }
.fa-brands { .fa-brands {
@@ -23679,26 +23639,42 @@ font-size: 0.8125rem;
} }
/* Position: floating — fixed sidebar, hidden on mobile */ /* Position: floating — fixed sidebar, hidden on mobile */
.moko-social-icons--floating { .moko-social-floating-wrap {
position: fixed; position: fixed;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
z-index: 1030; z-index: 1030;
display: flex; display: flex;
align-items: center;
transition: transform 0.3s ease;
}
.moko-social-floating-wrap--left {
left: 0;
flex-direction: row;
}
.moko-social-floating-wrap--right {
right: 0;
flex-direction: row-reverse;
}
.moko-social-floating-wrap--left.is-collapsed {
transform: translateY(-50%) translateX(calc(-100% + 1.5rem));
}
.moko-social-floating-wrap--right.is-collapsed {
transform: translateY(-50%) translateX(calc(100% - 1.5rem));
}
.moko-social-icons--floating {
display: flex;
} }
.moko-social-icons--floating ul { .moko-social-icons--floating ul {
flex-direction: column; flex-direction: column;
} }
.moko-social-icons--floating-left {
left: 0;
}
.moko-social-icons--floating-right {
right: 0;
}
.moko-social-icons--floating-left a { .moko-social-icons--floating-left a {
border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0; border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0;
} }
@@ -23712,8 +23688,48 @@ font-size: 0.8125rem;
border-radius: inherit; border-radius: inherit;
} }
/* Floating toggle button */
.moko-social-floating-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 2.5rem;
padding: 0;
border: 1px solid var(--social-icon-bg, var(--border-color, hsl(0, 0%, 80%)));
background: var(--social-icon-bg, var(--body-bg, hsl(0, 0%, 100%)));
color: var(--social-icon-color, currentColor);
cursor: pointer;
font-size: 0.65rem;
transition: background 0.2s, color 0.2s;
flex-shrink: 0;
}
.moko-social-floating-wrap--left .moko-social-floating-toggle {
border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0;
border-left: 0;
}
.moko-social-floating-wrap--right .moko-social-floating-toggle {
border-radius: var(--social-icon-radius, 0.375rem) 0 0 var(--social-icon-radius, 0.375rem);
border-right: 0;
}
.moko-social-floating-toggle:hover {
background: var(--social-icon-hover-bg, var(--accent-color-primary, hsl(220, 70%, 50%)));
color: #fff;
}
.moko-social-floating-toggle .fa-solid {
transition: transform 0.3s ease;
}
.is-collapsed .moko-social-floating-toggle .fa-solid {
transform: rotate(180deg);
}
@media (max-width: 991.98px) { @media (max-width: 991.98px) {
.moko-social-icons--floating { .moko-social-floating-wrap {
display: none; display: none;
} }
} }
+49
View File
@@ -789,6 +789,53 @@
}); });
} }
// ========================================================================
// FLOATING SOCIAL BAR (collapsible)
// ========================================================================
var socialStorageKey = "moko-social-collapsed";
function initSocialFloating() {
var wrap = doc.getElementById("mokoSocialFloating");
var toggle = doc.getElementById("mokoSocialFloatingToggle");
// The toggle is only rendered when the "collapsible" param is enabled.
if (!wrap || !toggle) return;
// Resolve the initial collapsed state:
// - A returning visitor's explicit choice (localStorage) always wins.
// - Otherwise fall back to the admin's "collapsed by default" setting,
// which the layout also renders server-side to avoid a state flash.
var saved = null;
try { saved = localStorage.getItem(socialStorageKey); } catch (e) {}
var collapsed = (saved === "1" || saved === "0")
? (saved === "1")
: (wrap.getAttribute("data-default-collapsed") === "1");
wrap.classList.toggle("is-collapsed", collapsed);
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
toggle.addEventListener("click", function () {
var isCollapsed = wrap.classList.toggle("is-collapsed");
toggle.setAttribute("aria-expanded", isCollapsed ? "false" : "true");
try { localStorage.setItem(socialStorageKey, isCollapsed ? "1" : "0"); } catch (e) {}
});
}
// ========================================================================
// SOCIAL ICON TOOLTIPS
// ========================================================================
function initSocialTooltips() {
var triggers = doc.querySelectorAll('.moko-social-icons [data-bs-toggle="tooltip"]');
if (!triggers.length) return;
// Bootstrap 5 tooltip init
if (typeof bootstrap !== "undefined" && bootstrap.Tooltip) {
triggers.forEach(function (el) {
new bootstrap.Tooltip(el, { trigger: "hover focus" });
});
}
}
/** /**
* Run all template JS initializations * Run all template JS initializations
*/ */
@@ -815,6 +862,8 @@
initBackTop(); initBackTop();
initSearchToggle(); initSearchToggle();
initSidebarAccordion(); initSidebarAccordion();
initSocialFloating();
initSocialTooltips();
initVarCopy(); initVarCopy();
} }
+38 -24
View File
@@ -35,13 +35,13 @@
</updateservers> </updateservers>
<dlid prefix="dlid=" suffix=""/> <dlid prefix="dlid=" suffix=""/>
<name>mokoonyx</name> <name>mokoonyx</name>
<version>02.25.00</version> <version>02.29.00</version>
<scriptfile>script.php</scriptfile> <scriptfile>script.php</scriptfile>
<creationDate>2026-05-16</creationDate> <creationDate>2026-05-16</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright> <copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
<description><![CDATA[<p><img src="https://img.shields.io/gitea/v/release/MokoConsulting/MokoOnyx?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&amp;logo=gitea&amp;logoColor=white&amp;label=version" alt="Version" /> <img src="https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&amp;logoColor=white" alt="License" /> <img src="https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&amp;logoColor=white" alt="Joomla" /> <img src="https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&amp;logoColor=white" alt="PHP" /></p> <h3>MokoOnyx</h3> <p> <strong>MokoOnyx</strong> is a modern, lightweight enhancement layer built on Joomla's Cassiopeia template. It adds Font Awesome 7, Bootstrap 5 helpers, automatic Table of Contents, advanced Dark Mode theming, and optional Google Tag Manager / GA4 integration — all with minimal core overrides for maximum upgrade compatibility. </p> <h4>Custom Colour Themes</h4> <p> Copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code> (or dark equivalent), customise the CSS variables, then select "Custom" in the Theme tab. </p> <h4>Custom CSS &amp; JavaScript</h4> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — custom CSS overrides</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — custom JavaScript</li> </ul> <p>These files survive template updates.</p>]]></description> <description><![CDATA[<p><img src="https://img.shields.io/git/v/release/MokoConsulting/MokoOnyx?git_url=https%3A%2F%2Fgit.mokoconsulting.tech&amp;logo=git&amp;logoColor=white&amp;label=version" alt="Version" /> <img src="https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&amp;logoColor=white" alt="License" /> <img src="https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&amp;logoColor=white" alt="Joomla" /> <img src="https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&amp;logoColor=white" alt="PHP" /></p> <h3>MokoOnyx</h3> <p> <strong>MokoOnyx</strong> is a modern, lightweight enhancement layer built on Joomla's Cassiopeia template. It adds Font Awesome 7, Bootstrap 5 helpers, automatic Table of Contents, advanced Dark Mode theming, and optional Google Tag Manager / GA4 integration — all with minimal core overrides for maximum upgrade compatibility. </p> <h4>Custom Colour Themes</h4> <p> Copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code> (or dark equivalent), customise the CSS variables, then select "Custom" in the Theme tab. </p> <h4>Custom CSS &amp; JavaScript</h4> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — custom CSS overrides</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — custom JavaScript</li> </ul> <p>These files survive template updates.</p>]]></description>
<php_minimum>8.1.0</php_minimum> <php_minimum>8.1.0</php_minimum>
<inheritable>1</inheritable> <inheritable>1</inheritable>
<files> <files>
@@ -354,6 +354,20 @@
<option value="left">TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT</option> <option value="left">TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT</option>
<option value="right">TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT</option> <option value="right">TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT</option>
</field> </field>
<field name="social_floating_collapsible" type="radio" default="1"
label="TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_LABEL" description="TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSIBLE_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="social_floating:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field name="social_floating_collapsed" type="radio" default="1"
label="TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_LABEL" description="TPL_MOKOONYX_SOCIAL_FLOATING_COLLAPSED_DESC"
layout="joomla.form.field.radio.switcher" filter="boolean"
showon="social_floating:1[AND]social_floating_collapsible:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<!-- Style options --> <!-- Style options -->
<field name="social_sep_style" type="spacer" label="Style" hr="false" class="text fw-bold" <field name="social_sep_style" type="spacer" label="Style" hr="false" class="text fw-bold"
@@ -387,28 +401,28 @@
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" /> showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
<field name="social_urls_note" type="note" description="TPL_MOKOONYX_SOCIAL_URLS_NOTE" <field name="social_urls_note" type="note" description="TPL_MOKOONYX_SOCIAL_URLS_NOTE"
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" /> showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
<field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" /> <field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" validate="url" />
<field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" /> <field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" validate="url" />
<field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" /> <field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" validate="url" />
<field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" /> <field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" validate="url" />
<field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" /> <field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" validate="url" />
<field name="social_github_url" type="url" default="" label="GitHub" filter="url" /> <field name="social_github_url" type="url" default="" label="GitHub" filter="url" validate="url" />
<field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" /> <field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" validate="url" />
<field name="social_threads_url" type="url" default="" label="Threads" filter="url" /> <field name="social_threads_url" type="url" default="" label="Threads" filter="url" validate="url" />
<field name="social_discord_url" type="url" default="" label="Discord" filter="url" /> <field name="social_discord_url" type="url" default="" label="Discord" filter="url" validate="url" />
<field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" /> <field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" validate="url" />
<field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" /> <field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" validate="url" />
<field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" /> <field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" validate="url" />
<field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" /> <field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" validate="url" />
<field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" /> <field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" validate="url" />
<field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" /> <field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" validate="url" />
<field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" /> <field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" validate="url" />
<field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" /> <field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" validate="url" />
<field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" /> <field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" validate="url" />
<field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" /> <field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" validate="url" />
<field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" /> <field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" validate="url" />
<field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" /> <field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" validate="url" />
<field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" /> <field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" validate="url" />
<field name="social_mail_url" type="url" default="" label="Email" description="TPL_MOKOONYX_SOCIAL_MAIL_DESC" filter="string" /> <field name="social_mail_url" type="url" default="" label="Email" description="TPL_MOKOONYX_SOCIAL_MAIL_DESC" filter="string" />
</fieldset> </fieldset>
+2 -2
View File
@@ -136,11 +136,11 @@ A read-only reference tab displaying all available CSS custom properties organiz
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+2 -2
View File
@@ -223,11 +223,11 @@ Additional variables are defined for: VirtueMart (`--vm-*`), Gable (`--gab-*`),
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+2 -2
View File
@@ -114,11 +114,11 @@ For additional overrides beyond theme variables, use these files (also update-sa
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+8 -8
View File
@@ -46,7 +46,7 @@ MokoOnyx/
│ ├── dark.custom.css # Custom dark palette template │ ├── dark.custom.css # Custom dark palette template
│ └── brand-showcase.html # Brand showcase HTML template │ └── brand-showcase.html # Brand showcase HTML template
├── Makefile # Build and validation automation ├── Makefile # Build and validation automation
├── composer.json # PHP dependencies (moko-platform) ├── composer.json # PHP dependencies (mokocli)
├── package.json # Node.js dependencies (minification) ├── package.json # Node.js dependencies (minification)
├── phpcs.xml # PHP CodeSniffer configuration ├── phpcs.xml # PHP CodeSniffer configuration
├── phpstan.neon # PHPStan static analysis configuration ├── phpstan.neon # PHPStan static analysis configuration
@@ -63,7 +63,7 @@ MokoOnyx/
## Prerequisites ## Prerequisites
- **PHP** 8.1+ - **PHP** 8.1+
- **Composer** (for moko-platform CLI and dependencies) - **Composer** (for mokocli CLI and dependencies)
- **Node.js** (optional, for build-time minification with terser/clean-css) - **Node.js** (optional, for build-time minification with terser/clean-css)
- **Make** (GNU Make or compatible) - **Make** (GNU Make or compatible)
- **zip** (or PowerShell for Windows) - **zip** (or PowerShell for Windows)
@@ -110,7 +110,7 @@ Creates `dist/mokoonyx-{version}-beta.zip` (skips minification).
## Validation ## Validation
MokoOnyx uses the **moko-platform CLI** for code quality checks. MokoOnyx uses the **mokocli CLI** for code quality checks.
```bash ```bash
# Run all validation checks # Run all validation checks
@@ -144,7 +144,7 @@ After the release package is built:
1. Tag the release: `git tag {version}` 1. Tag the release: `git tag {version}`
2. Push tags: `git push origin --tags` 2. Push tags: `git push origin --tags`
3. Create a Gitea release and attach the ZIP from `dist/` 3. Create a MokoGIT release and attach the ZIP from `dist/`
### Generate checksums ### Generate checksums
@@ -185,7 +185,7 @@ Creates SHA-256 checksums for all ZIP files in `dist/`.
## Contributing ## Contributing
1. Fork the repository on [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) 1. Fork the repository on [MokoGIT](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
2. Create a feature branch from `dev` 2. Create a feature branch from `dev`
3. Make your changes in `src/` 3. Make your changes in `src/`
4. Run `make validate` to ensure all checks pass 4. Run `make validate` to ensure all checks pass
@@ -245,7 +245,7 @@ When a PR is merged from `dev` to `main`:
1. The minor version is bumped (e.g., `02.08.xx` -> `02.09.00`) 1. The minor version is bumped (e.g., `02.08.xx` -> `02.09.00`)
2. Stability suffixes are stripped (stable release) 2. Stability suffixes are stripped (stable release)
3. A Gitea release is created with the build package (ZIP + tar.gz + SHA-256) 3. A MokoGIT release is created with the build package (ZIP + tar.gz + SHA-256)
4. `updates.xml` is updated for the Joomla update server 4. `updates.xml` is updated for the Joomla update server
5. The `dev` branch is recreated from `main` 5. The `dev` branch is recreated from `main`
6. A `version/XX.YY.ZZ` archive branch is created 6. A `version/XX.YY.ZZ` archive branch is created
@@ -277,11 +277,11 @@ vendor/bin/codecept run
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+185
View File
@@ -0,0 +1,185 @@
[← Back to Home](Home)
# Configuration
MokoOnyx is configured through the Joomla template style editor at **System → Site Templates → MokoOnyx**. Parameters are organized into tabbed fieldsets.
---
## Migration Tab
Informational notes about the MokoCassiopeia migration process. No editable parameters -- this tab provides guidance only.
---
## Advanced Tab
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `developmentmode` | Toggle | Off | Enables development mode. When on, `.min` files are deleted and unminified source files are served. When off, minified files are auto-generated. |
| `fluidContainer` | Toggle | Static | Controls whether the main layout uses a fixed-width (`container`) or full-width (`container-fluid`) wrapper. |
---
## Favicon Tab
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `favicon_source` | Media picker | _(none)_ | Upload a PNG image. MokoOnyx auto-generates all required favicon sizes, `apple-touch-icon.png`, and `site.webmanifest` on the next page load. |
---
## Google Tab
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `googletagmanager` | Toggle | Off | Enable Google Tag Manager integration. |
| `googletagmanagerid` | Text | _(empty)_ | Your GTM container ID (e.g., `GTM-XXXXXXX`). Shown when GTM is enabled. |
| `googleanalytics` | Toggle | Off | Enable Google Analytics (GA4) integration. |
| `googleanalyticsid` | Text | _(empty)_ | Your GA4 measurement ID (e.g., `G-XXXXXXXXXX`). Shown when GA4 is enabled. |
| `googlesitekey` | Text | _(empty)_ | Google site verification meta tag value. |
| `googlevisitordetection` | Toggle | On | When enabled, sends visitor context to GTM/GA4: login status, user group, and page type. Shown when GTM or GA4 is enabled. |
---
## Custom Code Tab
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `custom_head_start` | Textarea | _(empty)_ | Raw HTML/JS injected at the **start** of `<head>`. Useful for consent managers or early scripts. |
| `custom_head_end` | Textarea | _(empty)_ | Raw HTML/JS injected at the **end** of `<head>`, before `</head>`. |
---
## Drawers Tab
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `drawerLeftIcon` | Text | `fa-solid fa-chevron-right` | Font Awesome class for the left drawer toggle button icon. |
| `drawerRightIcon` | Text | `fa-solid fa-chevron-left` | Font Awesome class for the right drawer toggle button icon. |
---
## Theme Tab
The Theme tab is the largest configuration area, organized into sub-sections.
### General
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `theme_enabled` | Toggle | On | Master switch for the theme system (light/dark mode). |
| `theme_control_type` | List | Radios | How the user switches themes: **Switch** (Light/Dark toggle), **Radios** (Light/Dark/System), or **None** (no visible control). |
| `theme_default_choice` | List | System | Default theme when no user preference is stored: **System**, **Light**, or **Dark**. |
| `theme_auto_dark` | Toggle | Off | Automatically switch to dark mode based on time of day. |
| `theme_meta_color_scheme` | Toggle | On | Output `<meta name="color-scheme">` tag for browser chrome theming. |
| `theme_meta_theme_color` | Toggle | On | Output `<meta name="theme-color">` tag for mobile browser address bar. |
| `theme_bridge_bs_aria` | Toggle | On | Bridge Bootstrap `data-bs-theme` attribute with ARIA attributes for screen readers. |
### Variables and Palettes
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `colorLightName` | List | Standard | Light theme palette: **Standard** (built-in) or **Custom** (loads `light.custom.css`). |
| `colorDarkName` | List | Standard | Dark theme palette: **Standard** (built-in) or **Custom** (loads `dark.custom.css`). |
See [Custom Themes](Custom-Themes) for how to create custom palettes.
### Typography
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `useFontScheme` | Grouped list | Roboto (local) | Font family. Options: **None**, **Roboto** (local), **Noto Sans** (local), **Fira Sans** (local). All local fonts are self-hosted for GDPR compliance. |
### Branding and Icons
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `brand` | Toggle | On | Show the brand/logo area in the header. |
| `logoFile` | Media picker | _(none)_ | Logo image file. Shown when brand is enabled. |
| `siteTitle` | Text | MokoOnyx | Site title displayed next to the logo. |
| `siteDescription` | Text | _(empty)_ | Tagline displayed below the site title. |
| `fA6KitCode` | Text | _(empty)_ | Font Awesome 7 Pro Kit code. If empty, the bundled Font Awesome 7 Free is used (2,000+ icons). |
### Header and Navigation
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `stickyHeader` | Toggle | Off | Make the header stick to the top of the viewport on scroll. |
| `backTop` | Toggle | Off | Show a "Back to Top" button when the user scrolls down. |
### Accessibility
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `a11y_toolbar_enabled` | Toggle | On | Master switch for the accessibility toolbar. |
| `a11y_text_resize` | Toggle | On | Allow users to increase/decrease text size. |
| `a11y_color_inversion` | Toggle | On | Allow users to invert page colors. |
| `a11y_high_contrast` | Toggle | On | Allow users to enable high-contrast mode. |
| `a11y_highlight_links` | Toggle | On | Allow users to highlight all links on the page. |
| `a11y_readable_font` | Toggle | On | Allow users to switch to a more readable font. |
| `a11y_pause_animations` | Toggle | On | Allow users to pause CSS animations. |
| `a11y_toolbar_pos` | Hidden | Bottom-right | Toolbar position (fixed to bottom-right in code). |
### Theme Toggle UI
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `theme_fab_enabled` | Toggle | On | Show the floating action button (FAB) for theme switching. |
| `theme_fab_pos` | Hidden | Bottom-right | FAB position (fixed to bottom-right in code). |
---
## Social Icons Tab
Configure social media icon links displayed in the topbar, footer, and/or as a floating sidebar.
### Display Positions
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `social_topbar` | Toggle | Off | Show social icons in the topbar. |
| `social_footer` | Toggle | Off | Show social icons in the footer. |
| `social_floating` | Toggle | Off | Show social icons as a fixed floating sidebar (hidden on mobile). |
| `social_floating_pos` | List | Left | Which side of the screen the floating bar appears on. Shown when floating is enabled. |
The floating sidebar includes a **collapsible toggle** — visitors can click the chevron to collapse/expand the bar. The collapsed state is remembered across page loads via localStorage.
### Style Options
Shown when at least one display position is enabled.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `social_icon_style` | List | Plain | Icon shape: **Plain**, **Square**, **Circle**, or **Rounded**. |
| `social_icon_size` | List | Medium | Icon size: **Small**, **Medium**, or **Large**. |
| `social_icon_color` | List | Theme | Colour scheme: **Theme** (CSS variables), **Brand** (platform colours), **Black**, or **White**. |
### Platform URLs
All URL fields use Joomla's `type="url"` with server-side `validate="url"` validation. Invalid URLs are silently skipped at render time and logged via `Joomla\CMS\Log\Log` for debugging.
Supported platforms: Facebook, X/Twitter, Instagram, LinkedIn, YouTube, GitHub, Bluesky, Threads, Discord, TikTok, Reddit, Pinterest, Snapchat, Telegram, WhatsApp, Tumblr, Twitch, Spotify, SoundCloud, Flickr, Vimeo, Linktree, and Email (`mailto:` or contact page URL).
### Tooltips
All social icon links display a **Bootstrap tooltip** on hover showing the platform name (e.g. "Follow us on GitHub"). Tooltip placement is context-aware — floating-left bars show tooltips on the right, floating-right on the left, and topbar/footer icons show tooltips above.
---
## CSS Variables Tab
A read-only reference tab displaying all available CSS custom properties organized by category. See the [CSS Variables](CSS-Variables) page for the full reference.
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
| 1.1 | 2026-06-20 | Moko Consulting | Added Social Icons tab documentation |
+125
View File
@@ -0,0 +1,125 @@
[← Back to Home](Home)
# Custom Themes
MokoOnyx ships with **standard** light and dark palettes. You can create fully custom colour palettes by overriding CSS variables in dedicated files.
---
## How It Works
1. MokoOnyx loads a theme CSS file based on the `colorLightName` and `colorDarkName` template parameters
2. When set to **Standard**, it loads `light.standard.css` or `dark.standard.css`
3. When set to **Custom**, it loads `light.custom.css` or `dark.custom.css`
4. The theme file sets all CSS variables inside `:root[data-bs-theme="light"]` or `:root[data-bs-theme="dark"]`
---
## Creating a Custom Palette
### Step 1: Copy the Template
MokoOnyx ships starter templates in the `templates/` directory within the template source:
```
templates/mokoonyx/templates/light.custom.css
templates/mokoonyx/templates/dark.custom.css
```
These are full copies of the standard palettes -- every variable is included so you have a complete starting point.
### Step 2: Place Files in the Media Directory
Copy your customized files to the **media** directory (this location survives template updates):
```
media/templates/site/mokoonyx/css/theme/light.custom.css
media/templates/site/mokoonyx/css/theme/dark.custom.css
```
### Step 3: Select "Custom" in Template Settings
1. Go to **System → Site Templates → MokoOnyx**
2. Open the **Theme** tab
3. Under **Variables & Palettes**:
- Set **Light Theme** to **Custom**
- Set **Dark Theme** to **Custom**
4. Save
---
## File Locations
| File | Path | Purpose |
|------|------|---------|
| Light standard | `media/templates/site/mokoonyx/css/theme/light.standard.css` | Built-in light palette (do not edit) |
| Dark standard | `media/templates/site/mokoonyx/css/theme/dark.standard.css` | Built-in dark palette (do not edit) |
| Light custom | `media/templates/site/mokoonyx/css/theme/light.custom.css` | Your custom light palette |
| Dark custom | `media/templates/site/mokoonyx/css/theme/dark.custom.css` | Your custom dark palette |
| Light template | `templates/mokoonyx/templates/light.custom.css` | Starter template (copy from here) |
| Dark template | `templates/mokoonyx/templates/dark.custom.css` | Starter template (copy from here) |
---
## Key Variables to Customize
The most impactful variables to change for a quick rebrand:
```css
:root[data-bs-theme="light"] {
/* Brand colours -- the foundation of the entire palette */
--color-primary: #112855;
--accent-color-primary: #3f8ff0;
--accent-color-secondary: #6fb3ff;
/* Body */
--body-color: #22262a;
--body-bg: #fff;
/* Links */
--color-link: #224FAA;
--link-color: #224faa;
--link-hover-color: #424077;
/* Navigation */
--mainmenu-nav-link-color: white;
--nav-text-color: white;
--nav-bg-color: var(--color-link);
/* Header */
--header-background-color: #adadad;
}
```
See the [CSS Variables](CSS-Variables) page for a complete reference of all available variables.
---
## Variable Sync on Update
When MokoOnyx is updated, the installer runs a **CSS variable sync** process. If new variables have been added to the standard palettes, they are automatically appended to your custom palette files with their default values. A notice is displayed in the admin panel telling you how many variables were added.
This ensures your custom palettes stay compatible with new template features without breaking existing customizations.
---
## Custom CSS and JavaScript
For additional overrides beyond theme variables, use these files (also update-safe):
| File | Path |
|------|------|
| Custom CSS | `media/templates/site/mokoonyx/css/user.css` |
| Custom JS | `media/templates/site/mokoonyx/js/user.js` |
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
+66
View File
@@ -0,0 +1,66 @@
[← Back to Home](Home)
# Installation
## Requirements
| Requirement | Minimum Version |
|-------------|----------------|
| Joomla | 5.x or 6.x |
| PHP | 8.1 or higher |
## Download
Download the latest `mokoonyx-{version}.zip` from the [Releases page](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable).
## Install via Joomla Admin
1. Log in to your Joomla administrator panel
2. Navigate to **System → Install → Extensions**
3. Upload the `mokoonyx-{version}.zip` file
4. Wait for the installer to complete
The installer performs a preflight check to verify your PHP and Joomla versions meet the minimum requirements. If they do not, installation will be blocked with an error message.
## Set as Default Template
1. Navigate to **System → Site Templates**
2. Click the star icon next to **MokoOnyx** to set it as the default site template
## First Page Load (Migration)
If you are migrating from MokoCassiopeia, the migration runs automatically during install:
1. **Visit any page on your site** after installation -- the template activates immediately
2. Check **administrator/logs/mokoonyx.log.php** to confirm the migration completed
3. Once confirmed, uninstall MokoCassiopeia from **Extensions → Manage**
See the [Migration](Migration) page for full details on what gets migrated.
## Post-Install Notes
- The extension is automatically **locked** after install to prevent accidental uninstallation
- Stale `.min` files from previous versions are cleaned automatically
- Favicon stamps are cleared so favicons regenerate on the next page load
- MokoCassiopeia references in article content and modules are automatically updated to MokoOnyx
## Update Server
MokoOnyx includes an automatic update server. Joomla will notify you when new versions are available via **System → Update → Extensions**. Two update servers are configured for redundancy:
| Priority | Server |
|----------|--------|
| 1 | Git (git.mokoconsulting.tech) |
| 2 | GitHub (github.com) |
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
+119
View File
@@ -0,0 +1,119 @@
[← Back to Home](Home)
# Migration from MokoCassiopeia
MokoOnyx is the successor to MokoCassiopeia. The migration runs **automatically during install/update** -- no manual steps are required beyond installing MokoOnyx.
---
## When Does Migration Run?
The migration runs in the `postflight()` method of the installer script (`script.php`), which executes during both `install` and `update` operations. It checks for existing MokoCassiopeia template styles in the database and only runs if they are found.
---
## What Gets Migrated
### 1. Template Styles and Parameters
- All MokoCassiopeia template styles are detected from the `#__template_styles` table
- For each MokoCassiopeia style, a matching MokoOnyx style is created with the same parameters
- The first MokoCassiopeia style's parameters are applied to the installer-created default MokoOnyx style
- Additional styles are created as new entries
- Parameter values are updated to replace `mokocassiopeia` references with `mokoonyx`
### 2. Default Template Assignment
If MokoCassiopeia was set as the default site template (`home = 1`), MokoOnyx automatically takes over as the default. The MokoCassiopeia style is unset as default.
### 3. Custom User Files
The following files are copied from the MokoCassiopeia media directory to MokoOnyx (only if the destination file does not already exist):
| File | Purpose |
|------|---------|
| `css/theme/light.custom.css` | Custom light palette |
| `css/theme/dark.custom.css` | Custom dark palette |
| `css/theme/light.custom.min.css` | Minified custom light palette |
| `css/theme/dark.custom.min.css` | Minified custom dark palette |
| `css/user.css` | Custom CSS overrides |
| `css/user.min.css` | Minified custom CSS |
| `js/user.js` | Custom JavaScript |
| `js/user.min.js` | Minified custom JavaScript |
Source: `media/templates/site/mokocassiopeia/`
Destination: `media/templates/site/mokoonyx/`
### 4. Content References
All references to "MokoCassiopeia" and "mokocassiopeia" in article content (`introtext` and `fulltext` columns) and module content are automatically replaced with "MokoOnyx" and "mokoonyx". This covers:
- Image paths (e.g., `media/templates/site/mokocassiopeia/...`)
- Template name references in custom HTML modules
- Any other text references in content
### 5. Additional Post-Install Tasks
These tasks run during every install/update (not just migration):
| Task | Description |
|------|-------------|
| **Favicon stamp cleared** | Deletes `.favicon_generated` stamp so favicons regenerate. Also removes the old `/images/favicons/` directory and root-level favicon files from previous versions. |
| **Media folder cleaned** | Removes stale `.min.css` and `.min.js` files (regenerated by `MokoMinifyHelper`), unminified vendor files, and deprecated files (`custom.css`, `custom.js`, `template-rtl.css`). |
| **Extension locked** | Sets `locked = 1` in `#__extensions` to prevent accidental uninstallation. |
---
## How to Trigger Migration
Migration runs automatically during installation. If you need to observe the results:
1. Install MokoOnyx via **System → Install → Extensions**
2. Go to **System → Site Templates** and verify MokoOnyx appears with your settings
3. Visit any page on your site to confirm the template is active
4. Check **administrator/logs/mokoonyx.log.php** for migration log entries
---
## Re-Running Migration
The migration runs on every install/update operation via the `postflight()` method. It is safe to re-run because:
- Style creation checks for existing styles before creating duplicates
- File copies only occur when the destination file does not exist
- Content replacements are idempotent (replacing "MokoOnyx" with "MokoOnyx" is a no-op)
To force a fresh migration, you can reinstall MokoOnyx by uploading the ZIP package again.
---
## After Migration
Once you have confirmed everything is working:
1. Uninstall MokoCassiopeia from **Extensions → Manage**
2. Optionally clean up any remaining MokoCassiopeia files in `media/templates/site/mokocassiopeia/`
---
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Settings not migrated | Check `administrator/logs/mokoonyx.log.php` for error messages. Verify MokoCassiopeia styles exist in `#__template_styles`. |
| Custom theme files missing | Verify files exist in `media/templates/site/mokocassiopeia/css/theme/`. The copy only runs if the destination does not already exist -- if MokoOnyx files are already present, they are not overwritten. |
| Content references not updated | Check the log for "Content replacement failed" or "Module replacement failed" warnings. Database permissions may be insufficient. |
| PHP version error | MokoOnyx requires PHP 8.1+. The preflight check blocks installation if this is not met. |
| Joomla version error | MokoOnyx requires Joomla 4.4+. The preflight check blocks installation if this is not met. |
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
+113
View File
@@ -0,0 +1,113 @@
[← Back to Home](Home)
# Minification
MokoOnyx includes an automatic CSS/JS minification system that generates `.min` files at runtime based on the development mode setting.
---
## How It Works
The minification system is implemented in `MokoMinifyHelper` (`helper/minify.php`). It is called from the template's `index.php` on every page load and follows a simple rule:
- **Development mode OFF** (production): `.min` files are generated from source files when the source is newer or the `.min` file is missing
- **Development mode ON**: all `.min` files are **deleted** so the browser loads unminified source files for debugging
---
## Files Managed
### CSS Files
| Source File | Minified Output |
|-------------|----------------|
| `css/template.css` | `css/template.min.css` |
| `css/offline.css` | `css/offline.min.css` |
| `css/editor.css` | `css/editor.min.css` |
| `css/a11y-high-contrast.css` | `css/a11y-high-contrast.min.css` |
| `css/user.css` | `css/user.min.css` |
| `css/theme/light.standard.css` | `css/theme/light.standard.min.css` |
| `css/theme/dark.standard.css` | `css/theme/dark.standard.min.css` |
| `css/theme/light.custom.css` | `css/theme/light.custom.min.css` |
| `css/theme/dark.custom.css` | `css/theme/dark.custom.min.css` |
### JavaScript Files
| Source File | Minified Output |
|-------------|----------------|
| `js/template.js` | `js/template.min.js` |
| `js/gtm.js` | `js/gtm.min.js` |
| `js/user.js` | `js/user.min.js` |
All paths are relative to `media/templates/site/mokoonyx/`.
---
## Staleness Check
The system uses filesystem modification times to determine whether a `.min` file needs regenerating:
1. If the source file does not exist, skip it
2. If the `.min` file exists and its modification time is **newer or equal** to the source, skip it
3. Otherwise, read the source, minify it, and write the `.min` file
This means minification only runs when source files actually change, keeping page load overhead minimal.
---
## CSS Minification
The CSS minifier performs these transformations:
1. Remove CSS comments (preserving IE hacks)
2. Remove whitespace around `{ } : ; , > + ~`
3. Collapse remaining whitespace to single spaces
4. Remove trailing semicolons before closing braces (`;}` becomes `}`)
5. Strip leading/trailing whitespace
---
## JavaScript Minification
The JS minifier performs these transformations:
1. Remove multi-line comments (`/* ... */`)
2. Remove single-line comments (`//`) while preserving URLs
3. Collapse whitespace
4. Remove spaces around operators and punctuation
5. Restore necessary spaces after keywords (`var`, `let`, `const`, `return`, `typeof`, `instanceof`, `new`, `delete`, `throw`, `case`, `in`, `of`)
---
## Build-Time vs Runtime
| Context | What Happens |
|---------|-------------|
| **Runtime** (page load) | `MokoMinifyHelper::sync()` runs on every page load. In production mode, it checks timestamps and regenerates stale `.min` files. The overhead is negligible (filesystem stat calls only when files have not changed). |
| **Build-time** (`make build`) | The Makefile runs `make minify` before packaging. This uses the moko-platform `minify.js` script (Node.js) with `terser` and `clean-css` for higher-quality minification. |
| **Install/Update** | The installer script (`script.php`) deletes all `.min.css` and `.min.js` files in the `css/` and `js/` directories so they are cleanly regenerated by the runtime minifier on the first page load. Vendor `.min` files in `vendor/` are not touched. |
---
## Debug Mode Behaviour
To debug CSS or JS issues:
1. Go to **System → Site Templates → MokoOnyx**
2. Open the **Advanced** tab
3. Set **Development Mode** to **Yes**
4. Save
This immediately deletes all `.min` files. The template will load the unminified source files, making browser DevTools inspection straightforward. Remember to turn development mode **off** for production.
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
+5 -5
View File
@@ -12,7 +12,7 @@ A modern, lightweight Joomla site template built on Cassiopeia with Font Awesome
| **PHP** | 8.1+ | | **PHP** | 8.1+ |
| **License** | GPL-3.0-or-later | | **License** | GPL-3.0-or-later |
| **Replaces** | MokoCassiopeia (auto-migrates on install) | | **Replaces** | MokoCassiopeia (auto-migrates on install) |
| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) (primary) | | **Platform** | [MokoGIT](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) (primary) |
--- ---
@@ -81,18 +81,18 @@ Key parameters include:
| Repo | Purpose | | Repo | Purpose |
|------|---------| |------|---------|
| [Template-Client-WaaS](https://git.mokoconsulting.tech/MokoConsulting/Template-Client-WaaS/wiki) | Client site template (extends MokoOnyx) | | [Template-Client-MokoSuite](https://git.mokoconsulting.tech/MokoConsulting/Template-Client-MokoSuite/wiki) | Client site template (extends MokoOnyx) |
| [MokoWaaS](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki) | WaaS system plugin | | [MokoSuite](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki) | MokoSuite system plugin |
| [joomla-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp/wiki) | Joomla Web Services API MCP | | [joomla-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp/wiki) | Joomla Web Services API MCP |
| [deploy-mcp](https://git.mokoconsulting.tech/MokoConsulting/deploy-mcp/wiki) | Git-based deployment MCP | | [deploy-mcp](https://git.mokoconsulting.tech/MokoConsulting/deploy-mcp/wiki) | Git-based deployment MCP |
--- ---
> **[moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)** -- central standards hub for all Moko Consulting projects. > **[mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki)** -- central standards hub for all Moko Consulting projects.
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+3 -3
View File
@@ -50,16 +50,16 @@ MokoOnyx includes an automatic update server. Joomla will notify you when new ve
| Priority | Server | | Priority | Server |
|----------|--------| |----------|--------|
| 1 | Gitea (git.mokoconsulting.tech) | | 1 | MokoGIT (git.mokoconsulting.tech) |
| 2 | GitHub (github.com) | | 2 | GitHub (github.com) |
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+2 -2
View File
@@ -108,11 +108,11 @@ Once you have confirmed everything is working:
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+3 -3
View File
@@ -84,7 +84,7 @@ The JS minifier performs these transformations:
| Context | What Happens | | Context | What Happens |
|---------|-------------| |---------|-------------|
| **Runtime** (page load) | `MokoMinifyHelper::sync()` runs on every page load. In production mode, it checks timestamps and regenerates stale `.min` files. The overhead is negligible (filesystem stat calls only when files have not changed). | | **Runtime** (page load) | `MokoMinifyHelper::sync()` runs on every page load. In production mode, it checks timestamps and regenerates stale `.min` files. The overhead is negligible (filesystem stat calls only when files have not changed). |
| **Build-time** (`make build`) | The Makefile runs `make minify` before packaging. This uses the moko-platform `minify.js` script (Node.js) with `terser` and `clean-css` for higher-quality minification. | | **Build-time** (`make build`) | The Makefile runs `make minify` before packaging. This uses the mokocli `minify.js` script (Node.js) with `terser` and `clean-css` for higher-quality minification. |
| **Install/Update** | The installer script (`script.php`) deletes all `.min.css` and `.min.js` files in the `css/` and `js/` directories so they are cleanly regenerated by the runtime minifier on the first page load. Vendor `.min` files in `vendor/` are not touched. | | **Install/Update** | The installer script (`script.php`) deletes all `.min.css` and `.min.js` files in the `css/` and `js/` directories so they are cleanly regenerated by the runtime minifier on the first page load. Vendor `.min` files in `vendor/` are not touched. |
--- ---
@@ -102,11 +102,11 @@ This immediately deletes all `.min` files. The template will load the unminified
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
+61
View File
@@ -0,0 +1,61 @@
## Code of Conduct
This Code of Conduct establishes expectations for behavior within the MokoOnyx project community. The objective is to maintain a professional, inclusive, and respectful environment aligned with open source governance best practices.
## Scope
This Code of Conduct applies to all project spaces, including:
* Repositories, issues, pull requests, discussions, and security advisories.
* Project documentation, workflows, and release processes.
* Any communication channels officially associated with the project.
## Our Standards
Participants are expected to:
* Communicate professionally and respectfully.
* Provide constructive feedback focused on technical merit and project objectives.
* Respect differing viewpoints, experience levels, and backgrounds.
* Follow documented contribution, security, and governance policies.
Unacceptable behavior includes:
* Harassment, discrimination, or exclusionary conduct.
* Personal attacks, insults, or inflammatory comments.
* Publishing private information without consent.
* Disruptive behavior that materially interferes with project operations.
## Enforcement Responsibilities
Project maintainers are responsible for:
* Clarifying standards when questions arise.
* Taking appropriate and proportionate corrective action when violations occur.
* Maintaining confidentiality to the extent practical during investigations.
## Reporting
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported through:
* Email: `hello@mokoconsulting.tech` with subject `CODE OF CONDUCT: MokoOnyx`.
Reports should include relevant context, links, screenshots, or other supporting information.
## Enforcement Guidelines
Corrective actions may include, but are not limited to:
* Private warning or request for corrective action.
* Temporary or permanent restriction from project participation.
* Removal of content that violates this Code of Conduct.
Decisions are made based on impact, severity, and pattern of behavior.
## No Retaliation
Retaliation against individuals who report concerns in good faith is not tolerated. Any retaliatory behavior will be treated as a separate violation.
## Jurisdiction
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
+92
View File
@@ -0,0 +1,92 @@
## Contributing
This document defines how to contribute to Moko Consulting projects.
## Branching Strategy
All repositories follow this branching model:
```
feature/* --> dev --> main
```
| Branch | Purpose | Direct push? |
|--------|---------|-------------|
| `main` | Production/release branch | No -- PR merge only |
| `dev` | Integration branch | No -- PR merge only (CI bot whitelisted) |
| `feature/*` | Feature/bugfix work | Yes -- create freely |
| `rc/*` | Release candidates | No -- PR merge only |
| `alpha/*` | Alpha pre-releases | No -- PR merge only |
| `beta/*` | Beta pre-releases | No -- PR merge only |
| `version/*` | Release archive branches | Auto-created by CI |
### Workflow
1. Create a `feature/*` branch from `dev`
2. Make changes on the feature branch
3. Open a PR from `feature/*` to `dev`
4. After review, merge to `dev`
5. When ready for release, open a PR from `dev` to `main`
6. Merging to `main` triggers the auto-release pipeline
### Automated syncing
- `cascade-dev.yml` automatically forward-merges `main` to `dev` after every push to main
- If there are conflicts, a PR is created automatically
## Prerequisites
Contributors are expected to:
* Have a working understanding of the project's platform (Joomla or generic)
* Be familiar with Git and pull request workflows
* Review repository governance documents prior to submitting changes
### Quick Setup
```bash
git clone https://git.mokoconsulting.tech/MokoConsulting/<repo>.git
cd <repo>
git checkout dev
git checkout -b feature/my-change
```
## Coding and Formatting Standards
All contributions must:
* Follow platform coding standards (Joomla, PHP PSR) where applicable
* Conform to Moko Consulting repository standards for headers, metadata, and file structure
* Keep YAML workflow files ASCII-only (no em dashes, arrows, or emoji)
## Commit Messages
Commit messages should:
* Use conventional commits format: `type(scope): description`
* Types: feat, fix, chore, docs, refactor, test, ci
* Focus on what changed and why
* Include `[skip ci]` suffix for documentation-only changes
## Reporting Issues
Bug reports and enhancement requests should be filed as Git issues and include:
* Clear reproduction steps or use cases
* Expected versus actual behavior
* Relevant environment details
Security issues must follow the process in SECURITY.md and must not be reported publicly.
## Review Process
All pull requests are subject to review. Review criteria include:
* Technical correctness
* Alignment with project goals
* Maintainability and clarity
* Risk introduced to release and update processes
## License
By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later, consistent with the rest of the project.
+249
View File
@@ -0,0 +1,249 @@
[← Back to Home](Home)
# CSS Variables Reference
All CSS variables are defined in the theme palette files (`light.standard.css` / `dark.standard.css`) under `:root[data-bs-theme="light"]` or `:root[data-bs-theme="dark"]`. Override any of these in your [custom theme](Custom-Themes) files.
---
## Brand and Theme Colors
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--color-primary` | `#112855` | `#112855` | Primary brand colour |
| `--accent-color-primary` | `#3f8ff0` | `#3f8ff0` | Primary accent colour |
| `--accent-color-secondary` | `#6fb3ff` | `#6fb3ff` | Secondary accent colour |
## Typography and Body
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--body-font-family` | System sans-serif stack | System sans-serif stack | Base font family |
| `--body-font-size` | `1rem` | `1rem` | Base font size |
| `--body-font-weight` | `400` | `400` | Base font weight |
| `--body-line-height` | `1.5` | `1.5` | Base line height |
| `--body-color` | `#22262a` | `#e6ebf1` | Body text colour |
| `--body-bg` | `#fff` | `#0e1318` | Body background colour |
| `--heading-color` | `inherit` | `#f1f5f9` | Heading text colour |
| `--emphasis-color` | `#000` | `#fff` | Emphasis text colour |
| `--secondary-color` | `#22262abf` | `#e6ebf1bf` | Secondary text colour |
| `--tertiary-color` | `#22262a80` | `#e6ebf180` | Tertiary text colour |
| `--muted-color` | `#6d757e` | `#6d757e` | Muted text colour |
| `--highlight-color` | `#22262a` | `#111` | Highlight text colour |
| `--highlight-bg` | `#fbeea8` | `#ffe28a1a` | Highlight background |
## Standard Colors
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--blue` | `#010156` | `#91a4ff` |
| `--indigo` | `#6812f3` | `#b19cff` |
| `--purple` | `#6f42c2` | `#c0a5ff` |
| `--pink` | `#e93f8e` | `#ff8fc0` |
| `--red` | `#a51f18` | `#ff7a73` |
| `--orange` | `#fd7e17` | `#ff9c4d` |
| `--yellow` | `#ad6200` | `#ffd166` |
| `--green` | `#448344` | `#78d694` |
| `--teal` | `#5abfdd` | `#76e3ff` |
| `--cyan` | `#30638d` | `#6fb7ff` |
## Gray Scale
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--gray-100` | `#f9fafb` | `#161a20` |
| `--gray-200` | `#eaedf0` | `#1b2027` |
| `--gray-300` | `#dfe3e7` | `#222831` |
| `--gray-400` | `#ced4da` | `#2b323b` |
| `--gray-500` | `#adb5bd` | `#36404a` |
| `--gray-600` | `#6d757e` | `#48525d` |
| `--gray-700` | `#484f56` | `#5b6672` |
| `--gray-800` | `#353b41` | `#cfd6de` |
| `--gray-900` | `#22262a` | `#e6ebf1` |
## Links
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--color-link` | `#224FAA` | `white` | Navigation link colour |
| `--color-hover` | `var(--accent-color-primary)` | `gray` | Navigation hover colour |
| `--link-color` | `#224faa` | `#8ab4f8` | Content link colour |
| `--link-hover-color` | `#424077` | `#c3d6ff` | Content link hover colour |
| `--link-decoration` | `underline` | `underline` | Link text decoration |
## Navigation
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--mainmenu-nav-link-color` | `white` | `#fff` | Main menu active link colour |
| `--nav-text-color` | `white` | `gray` | Navigation text colour |
| `--nav-bg-color` | `var(--color-link)` | `var(--color-primary)` | Navigation background colour |
## Layout and Spacing
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--padding-x` | `0.15rem` | `0.15rem` | Horizontal padding |
| `--padding-y` | `0.15rem` | `0.15rem` | Vertical padding |
| `--secondary-bg` | `#eaedf0` | `#151b22` | Secondary background |
| `--tertiary-bg` | `#f9fafb` | `#10151b` | Tertiary background |
| `--nav-toggle-size` | `3rem` | `3rem` | Mobile nav toggle size |
## Breakpoints
| Variable | Value | Description |
|----------|-------|-------------|
| `--bp-xs` | `0` | Extra small |
| `--bp-sm` | `576px` | Small |
| `--bp-md` | `768px` | Medium |
| `--bp-lg` | `992px` | Large |
| `--bp-xl` | `1200px` | Extra large |
## Borders
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--border-width` | `1px` | `1px` |
| `--border-style` | `solid` | `solid` |
| `--border-color` | `#dfe3e7` | `#2b323b` |
| `--border-radius` | `.25rem` | `.25rem` |
| `--border-radius-sm` | `.2rem` | `.2rem` |
| `--border-radius-lg` | `.3rem` | `.3rem` |
| `--border-radius-pill` | `50rem` | `50rem` |
## Shadows
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--box-shadow` | `0 .5rem 1rem #00000026` | `0 .5rem 1rem #00000066` |
| `--box-shadow-sm` | `0 .125rem .25rem #00000013` | `0 .125rem .25rem #00000040` |
| `--box-shadow-lg` | `0 1rem 3rem #0000002d` | `0 1rem 3rem #00000080` |
## Header Background
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--header-background-color` | `#adadad` | `#1a1f2b` | Header fallback colour |
| `--header-background-image` | `url(.../bg.svg)` | `url(.../bg.svg)` | Header background image |
| `--header-background-attachment` | `fixed` | `fixed` | CSS attachment |
| `--header-background-repeat` | `repeat` | `repeat` | CSS repeat |
## Container Backgrounds
Each container position (below-topbar, top-a, top-b, sidebar, bottom-a, bottom-b) has these variables:
| Variable Pattern | Description |
|-----------------|-------------|
| `--container-{position}-bg-image` | Background image (default: `none`) |
| `--container-{position}-bg-color` | Background colour (default: `transparent`) |
| `--container-{position}-bg-position` | Background position |
| `--container-{position}-bg-attachment` | Background attachment |
| `--container-{position}-bg-repeat` | Background repeat |
| `--container-{position}-bg-size` | Background size |
| `--container-{position}-border` | Border style |
| `--container-{position}-border-radius` | Border radius |
## Forms
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--input-color` | `hsl(210, 11%, 15%)` | `#e6ebf1` |
| `--input-bg` | `hsl(210, 20%, 98%)` | `#1a2332` |
| `--input-border-color` | `hsl(210, 14%, 83%)` | `#3a4250` |
| `--input-focus-border-color` | `#8894aa` | `#5472ff` |
| `--form-valid-color` | `#448344` | `#78d694` |
| `--form-invalid-color` | `#a51f18` | `#ff8e86` |
## Bootstrap Palette
| Variable | Light Default | Dark Default |
|----------|--------------|-------------|
| `--primary` | `#010156` | `#010156` |
| `--secondary` | `#6d757e` | `#48525d` |
| `--success` | `#448344` | `#4aa664` |
| `--info` | `#30638d` | `#4f7aa0` |
| `--warning` | `#ad6200` | `#c77a00` |
| `--danger` | `#a51f18` | `#c23a31` |
| `--light` | `#f9fafb` | `#1b2027` |
| `--dark` | `#353b41` | `#0f1318` |
Each palette colour also has `-rgb`, `-text-emphasis`, `-bg-subtle`, and `-border-subtle` variants.
## Hero / Banner
| Variable | Description |
|----------|-------------|
| `--hero-height` | Banner height (default: `60vh`) |
| `--hero-color` | Banner text colour |
| `--hero-bg-repeat` | Background repeat |
| `--hero-bg-attachment` | Background attachment |
| `--hero-bg-position` | Background position |
| `--hero-bg-size` | Background size |
| `--hero-overlay-bg` | Overlay background colour |
| `--hero-primary-bg-color` | Primary variant background |
| `--hero-primary-overlay` | Primary variant overlay gradient |
| `--hero-secondary-bg-color` | Secondary variant background |
| `--hero-secondary-overlay` | Secondary variant overlay gradient |
| `--hero-card-*` | Hero card styling (bg, color, overlay, border-radius, padding, max-width) |
| `--hero-alt-card-*` | Alternative hero card styling |
## Block Colors
Used for top-a, top-b, bottom-a, bottom-b section backgrounds:
| Variable | Description |
|----------|-------------|
| `--block-color-1` through `--block-color-4` | Background colours |
| `--block-text-1` through `--block-text-4` | Text colours |
| `--block-highlight-bg` / `--block-highlight-text` | Highlight block |
| `--block-cta-bg` / `--block-cta-text` | Call-to-action block |
| `--block-alert-bg` / `--block-alert-text` | Alert block |
## Footer
| Variable | Default | Description |
|----------|---------|-------------|
| `--footer-padding-top` | `1rem` | Top padding |
| `--footer-padding-bottom` | `80px` | Bottom padding (accounts for FAB) |
| `--footer-grid-padding-y` | `2.5rem` | Grid vertical padding |
| `--footer-grid-padding-x` | `0.5em` | Grid horizontal padding |
## Theme FAB
| Variable | Light Default | Dark Default | Description |
|----------|--------------|-------------|-------------|
| `--theme-fab-bg` | `var(--color-primary)` | `#e6ebf1` | FAB background |
| `--theme-fab-color` | `#fff` | `#0e1318` | FAB icon colour |
| `--theme-fab-btn-bg` | `rgba(255,255,255,.15)` | `transparent` | FAB button background |
| `--theme-fab-border` | `rgba(255,255,255,0.3)` | `rgba(0,0,0,0.15)` | FAB border colour |
## Social Icons
| Variable | Default | Description |
|----------|---------|-------------|
| `--social-icon-size` | _(size preset)_ | Icon font size (overrides size preset) |
| `--social-icon-gap` | `0.5rem` | Gap between icons |
| `--social-icon-color` | `currentColor` | Icon colour |
| `--social-icon-hover-color` | `var(--accent-color-primary)` | Hover colour |
| `--social-icon-bg` | _(transparent)_ | Background for circle/rounded/square styles |
| `--social-icon-hover-bg` | _(varies)_ | Hover background |
| `--social-icon-radius` | `0.375rem` | Border radius for rounded style |
The floating bar's collapse toggle button inherits `--social-icon-bg`, `--social-icon-color`, and `--social-icon-hover-bg`.
## Component-Specific Variables
Additional variables are defined for: VirtueMart (`--vm-*`), Gable (`--gab-*`), accordion, alert, badge, breadcrumb, cards, dropdown, list group, modal, nav tabs/pills, offcanvas, pagination, popover, progress, spinner, table, toast, and tooltip components. See the theme palette files for the complete list.
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
| 1.1 | 2026-06-20 | Moko Consulting | Added Social Icons variables |
+289
View File
@@ -0,0 +1,289 @@
[← Back to Home](Home)
# Development
This page covers the repository structure, build process, release workflow, and contributing guidelines for MokoOnyx.
---
## Repository Structure
```
MokoOnyx/
├── src/ # Template source (this becomes the installable package)
│ ├── component.php # Component-only page layout
│ ├── error.php # Error page layout
│ ├── index.php # Main template entry point
│ ├── joomla.asset.json # Joomla Web Asset Manager definitions
│ ├── offline.php # Offline page layout
│ ├── script.php # Install/update/uninstall script
│ ├── sync_custom_vars.php # CSS variable sync for custom palettes
│ ├── templateDetails.xml # Joomla template manifest
│ ├── helper/ # PHP helper classes
│ │ └── minify.php # CSS/JS auto-minification
│ ├── html/ # Template overrides (modules, components, layouts)
│ ├── language/ # Language files (en-GB, en-US)
│ ├── media/ # Static assets (installed to media/templates/site/mokoonyx/)
│ │ ├── css/ # Stylesheets
│ │ │ ├── template.css # Main template stylesheet
│ │ │ ├── editor.css # Backend editor stylesheet
│ │ │ ├── offline.css # Offline page styles
│ │ │ ├── a11y-high-contrast.css # High-contrast accessibility mode
│ │ │ ├── user.css # User custom CSS (survives updates)
│ │ │ ├── fonts/ # Local font files (Roboto, Noto Sans, Fira Sans)
│ │ │ └── theme/ # Theme palette files
│ │ │ ├── light.standard.css
│ │ │ └── dark.standard.css
│ │ ├── js/ # JavaScript
│ │ │ ├── template.js # Main template JS
│ │ │ ├── gtm.js # Google Tag Manager integration
│ │ │ └── user.js # User custom JS (survives updates)
│ │ ├── images/ # Template images (bg.svg, etc.)
│ │ ├── fonts/ # Font files
│ │ └── vendor/ # Third-party assets (Font Awesome 7 Free)
│ └── templates/ # Starter files for users
│ ├── light.custom.css # Custom light palette template
│ ├── dark.custom.css # Custom dark palette template
│ └── brand-showcase.html # Brand showcase HTML template
├── Makefile # Build and validation automation
├── composer.json # PHP dependencies (moko-platform)
├── package.json # Node.js dependencies (minification)
├── phpcs.xml # PHP CodeSniffer configuration
├── phpstan.neon # PHPStan static analysis configuration
├── codeception.yml # Testing configuration
├── updates.xml # Joomla update server manifest
├── tests/ # Test suites
├── scripts/ # Build scripts
├── docs/ # Documentation source
└── build/ / dist/ # Build output (gitignored)
```
---
## Prerequisites
- **PHP** 8.1+
- **Composer** (for moko-platform CLI and dependencies)
- **Node.js** (optional, for build-time minification with terser/clean-css)
- **Make** (GNU Make or compatible)
- **zip** (or PowerShell for Windows)
---
## Setup
```bash
# Clone the repository
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
cd MokoOnyx
# Install dependencies
make install-deps
```
---
## Building Packages
### Build an installable ZIP
```bash
make build
```
This will:
1. Clean previous build artifacts (`build/` and `dist/`)
2. Minify CSS and JS assets
3. Copy `src/` contents to `build/package/`
4. Create `dist/mokoonyx-{version}.zip`
### Build a beta release
```bash
make build-beta
```
Creates `dist/mokoonyx-{version}-beta.zip` (skips minification).
---
## Validation
MokoOnyx uses the **moko-platform CLI** for code quality checks.
```bash
# Run all validation checks
make validate
# Individual checks
make lint # PHP syntax check
make check-joomla # Joomla manifest validation
make check-version # Version consistency across files
make check-xml # XML well-formedness
make check-headers # License header verification
make check-secrets # Credential leak scanning
# Full repository health check
make health
```
---
## Releasing
### Full release pipeline
```bash
make release
```
This runs the complete pipeline: `validate``build``checksum`
After the release package is built:
1. Tag the release: `git tag {version}`
2. Push tags: `git push origin --tags`
3. Create a Git release and attach the ZIP from `dist/`
### Generate checksums
```bash
make checksum
```
Creates SHA-256 checksums for all ZIP files in `dist/`.
---
## Available Make Targets
| Target | Description |
|--------|-------------|
| `make help` | Show all available targets |
| `make install-deps` | Install Composer dependencies |
| `make update-deps` | Update Composer dependencies |
| `make lint` | PHP syntax check |
| `make check-joomla` | Validate Joomla manifest |
| `make check-version` | Verify version consistency |
| `make check-headers` | Check license headers |
| `make check-secrets` | Scan for leaked credentials |
| `make check-xml` | Validate XML files |
| `make validate` | Run all validation checks |
| `make health` | Full repository health check |
| `make clean` | Clean build artifacts |
| `make minify` | Minify CSS/JS assets |
| `make build` | Build installable ZIP |
| `make build-beta` | Build beta release ZIP |
| `make checksum` | Generate SHA-256 checksums |
| `make release` | Full release pipeline |
| `make version` | Display version and extension info |
| `make security-check` | Composer security audit |
| `make all` | Full pipeline (deps, validate, build, checksum) |
---
## Contributing
1. Fork the repository on [Git](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
2. Create a feature branch from `dev`
3. Make your changes in `src/`
4. Run `make validate` to ensure all checks pass
5. Submit a pull request against `dev`
### Code Standards
- All PHP files must include the GPL-3.0-or-later license header
- PHP code must pass syntax checks and PHPStan analysis
- XML files must be well-formed
- Version numbers must be consistent across `README.md`, `templateDetails.xml`, and other version-bearing files
- Attribution goes to **Moko Consulting**, not individual contributors
### Branching Model
| Branch | Purpose | Stability Suffix |
|--------|---------|-----------------|
| `main` | Stable releases | (none) |
| `dev` | Active development | `-dev` |
| `alpha` | Alpha pre-releases | `-alpha` |
| `beta` | Beta pre-releases | `-beta` |
| `rc` | Release candidates | `-rc` |
| `feature/*` | Feature development | `-dev` |
| `version/XX.YY.ZZ` | Archived release snapshots | (none) |
### Version Numbering
Versions use the format `XX.YY.ZZ` (two-digit segments, zero-padded):
- **XX** -- Major version (breaking changes)
- **YY** -- Minor version (new features, bumped on release to main)
- **ZZ** -- Patch version (auto-incremented on every push to dev/feature branches)
Stability suffixes are appended during development:
- `02.09.00-dev` -- Development build
- `02.09.00-alpha` -- Alpha pre-release
- `02.09.00-beta` -- Beta pre-release
- `02.09.00-rc` -- Release candidate
On merge to `main`, suffixes are stripped and the minor version is bumped.
### Auto Version Bump
On every push to `dev`, `alpha`, `beta`, `rc`, or `feature/*`:
1. The patch version is incremented (e.g., `02.08.05` -> `02.08.06`)
2. The stability suffix is applied based on the branch name
3. All version-bearing files are updated (manifests, CHANGELOG, PHP headers, etc.)
4. A commit is created with `[skip ci]` to avoid infinite loops
Commits with `[skip ci]` or `[skip bump]` in the message are ignored.
### Auto Release
When a PR is merged from `dev` to `main`:
1. The minor version is bumped (e.g., `02.08.xx` -> `02.09.00`)
2. Stability suffixes are stripped (stable release)
3. A Git release is created with the build package (ZIP + tar.gz + SHA-256)
4. `updates.xml` is updated for the Joomla update server
5. The `dev` branch is recreated from `main`
6. A `version/XX.YY.ZZ` archive branch is created
### Release Channels
The `updates.xml` file provides update streams for Joomla sites:
| Channel | Tag | Description |
|---------|-----|-------------|
| Development | `dev` | Latest dev build (or stable if higher) |
| Alpha | `alpha` | Alpha pre-release |
| Beta | `beta` | Beta pre-release |
| Release Candidate | `rc` | RC pre-release |
| Stable | `stable` | Production-ready release |
Joomla sites check the channel matching their configured stability level.
---
## Testing
The repository includes a `codeception.yml` configuration for automated testing:
```bash
# Run tests (requires Codeception via Composer)
vendor/bin/codecept run
```
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
+78
View File
@@ -0,0 +1,78 @@
## Governance Overview
This document defines the governance framework for the MokoOnyx project. The objective is to ensure clear ownership, predictable decision making, and accountable stewardship across development, releases, and community interaction.
## Project Ownership
MokoOnyx is owned and maintained by **Moko Consulting**. Final authority for project direction, releases, and policy enforcement resides with the project owner.
## Roles and Responsibilities
### Maintainers
Maintainers are responsible for:
* Setting technical direction and release priorities.
* Reviewing and approving pull requests.
* Managing releases and distribution artifacts.
* Enforcing repository policies, including security and conduct requirements.
### Contributors
Contributors may:
* Submit pull requests and issues.
* Propose enhancements and report defects.
* Participate in technical discussions.
Contributors do not have merge authority unless explicitly granted.
## Decision Making
Decisions are made using a maintainers led model:
* Routine changes are approved through pull request review.
* Material changes affecting architecture, branding, licensing, or release processes require maintainer consensus.
* The project owner retains final decision authority if consensus cannot be reached.
## Change Management
Significant changes should:
* Be documented through issues or pull requests with clear rationale.
* Consider backward compatibility and upgrade impact.
* Include documentation updates when behavior or usage changes.
## Release Authority
Only maintainers may:
* Cut releases and publish artifacts.
* Update version numbers and manifests.
* Publish update metadata or advisories.
Release processes follow documented workflows and automation standards.
## Security Governance
Security issues are governed by the Security Policy (SECURITY.md in the repository). Maintainers are responsible for confidential handling, coordinated disclosure, and publication of advisories when appropriate.
## Conduct Enforcement
Behavior within the project is governed by the [Code of Conduct](Code-of-Conduct). Maintainers are responsible for enforcement actions and escalation handling.
## Conflict Resolution
Conflicts are handled through:
* Direct discussion between involved parties when appropriate.
* Maintainer mediation when necessary.
* Final determination by the project owner if required.
## External Dependencies
The project depends on Joomla core and other third party components. Governance of upstream projects remains outside the scope of this repository, but upstream changes may influence project decisions.
## Jurisdiction
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
+23
View File
@@ -0,0 +1,23 @@
# Roadmap
> Auto-generated from project milestones and issues.
> Last updated: 2026-05-11 17:09 UTC
## Active Milestones
_No active milestones._
## Backlog
_Issues not yet assigned to a milestone._
- [ ] feat: AI-generated page layouts from text prompts (#21) `type: feature`
- [ ] feat: Visual page builder with drag-and-drop (#20) `type: feature`
- [ ] feat: Export/import themes (#19) `type: feature`
- [ ] feat: CSS variable editor (#18) `type: feature`
- [ ] feat: Theme presets (#17) `type: feature`
- [ ] feat: Live preview customizer (#16) `type: feature`
- [ ] bug: site.webmanifest 404 — favicon generator not creating manifest (#1)
---
_Generated by [sync-roadmap-wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/actions) workflow._
+123
View File
@@ -0,0 +1,123 @@
[← Back to Home](Home)
# Template Overrides
MokoOnyx includes template overrides for Joomla core modules, third-party extensions, and layout files. These overrides are located in `templates/mokoonyx/html/`.
---
## Joomla Core Module Overrides
| Module | Override File(s) | Description |
|--------|-----------------|-------------|
| `mod_articles_archive` | `default.php` | Articles archive module |
| `mod_articles_categories` | `default.php` | Article categories listing |
| `mod_articles_category` | `default.php` | Articles from a single category |
| `mod_articles_latest` | `default.php` | Latest articles module |
| `mod_articles_news` | `default.php` | News flash / articles newsflash |
| `mod_articles_popular` | `default.php` | Most-read articles |
| `mod_banners` | `default.php` | Banner display module |
| `mod_breadcrumbs` | `default.php` | Breadcrumb navigation |
| `mod_custom` | `default.php`, `hero.php` | Custom HTML module (includes a hero layout variant) |
| `mod_feed` | `default.php` | RSS/Atom feed display |
| `mod_finder` | `default.php` | Smart Search box |
| `mod_footer` | `default.php` | Footer information |
| `mod_languages` | `default.php` | Language switcher |
| `mod_login` | `default.php` | Login form |
| `mod_menu` | `default.php`, `horizontal.php`, `horizontal_component.php`, `horizontal_heading.php`, `horizontal_separator.php`, `horizontal_url.php`, `mainmenu.php`, `mainmenu_component.php`, `mainmenu_heading.php`, `mainmenu_separator.php`, `mainmenu_url.php` | Menu module with horizontal and main menu layouts |
| `mod_random_image` | `default.php` | Random image display |
| `mod_related_items` | `default.php` | Related articles |
| `mod_stats` | `default.php` | Site statistics |
| `mod_syndicate` | `default.php` | Syndication / RSS link |
| `mod_tags_popular` | `default.php` | Popular tags |
| `mod_tags_similar` | `default.php` | Similar tags |
| `mod_users_latest` | `default.php` | Latest registered users |
| `mod_whosonline` | `default.php` | Who's online |
| `mod_wrapper` | `default.php` | iFrame wrapper |
---
## Third-Party Extension Overrides
### Community Builder
| Module | Override File(s) | Description |
|--------|-----------------|-------------|
| `mod_cblogin` | `default.php`, `default_logout.php` | Community Builder login/logout module |
| `mod_comprofilermoderator` | `default.php` | CB moderator panel |
| `mod_comprofileronline` | `default.php` | CB online users |
### DPCalendar
| Module | Override File(s) | Description |
|--------|-----------------|-------------|
| `mod_dpcalendar_counter` | `default.php` | Event countdown timer |
| `mod_dpcalendar_map` | `default.php` | Event map display |
| `mod_dpcalendar_mini` | `default.php`, `default_icons.php`, `default_map.php`, `default_quickadd.php`, `_scripts.php` | Mini calendar widget |
| `mod_dpcalendar_upcoming` | `default.php`, `_scripts.php` | Upcoming events list |
### JoomGallery
| Component View | Override File(s) | Description |
|---------------|-----------------|-------------|
| `com_joomgallery/category` | `default.php`, `default_cat.php` | Gallery category view |
| `com_joomgallery/gallery` | `default.php` | Main gallery view |
| `com_joomgallery/image` | `default.php` | Single image view |
---
## Component Overrides
| Component | View | Override File(s) | Description |
|-----------|------|-----------------|-------------|
| `com_content` | `article` | `toc-left.php`, `toc-right.php` | Article views with Table of Contents positioned left or right |
---
## Layout Overrides
| Layout | Override File | Description |
|--------|--------------|-------------|
| `joomla.module` | `card.php` | Module chrome -- wraps any module in a Bootstrap card layout |
---
## Hero Layout (mod_custom)
The `hero.php` layout for `mod_custom` provides a full-width hero/banner module. It uses the hero CSS variables (`--hero-*`) for styling. Assign a custom HTML module to positions like `banner` or `top-a` and select the "hero" layout in the module's Advanced tab.
---
## Menu Layouts
MokoOnyx provides two specialized menu layouts:
- **horizontal** -- A horizontal navigation bar with dropdown support, used for the main site navigation
- **mainmenu** -- An enhanced main menu layout with support for component links, headings, separators, and URL items
Each layout has sub-templates for different menu item types: `_component`, `_heading`, `_separator`, and `_url`.
---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting*
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Field | Value |
|---|---|
| Minimum Version | 04.07.00 |
| Platform | joomla |
| Revision | Date | Author | Description |
|---|---|---|---|
| 1.0 | 2026-05-16 | Moko Consulting | Standards compliance update |
+3 -3
View File
@@ -99,11 +99,11 @@ Each layout has sub-templates for different menu item types: `_component`, `_hea
--- ---
*Built with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform) -- Moko Consulting* *Built with [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -- Moko Consulting*
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description | | Revision | Date | Author | Description |
|---|---|---|---| |---|---|---|---|
@@ -111,7 +111,7 @@ Each layout has sub-templates for different menu item types: `_component`, `_hea
--- ---
*Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)* *Repo: [MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Field | Value | | Field | Value |
|---|---| |---|---|

Some files were not shown because too many files have changed in this diff Show More