617 Commits

Author SHA1 Message Date
jmiller 17c36e73d0 chore: sync .mokogitea/workflows/repo-health.yml from moko-platform [skip ci] 2026-06-03 09:37:01 +00:00
jmiller f602c54890 chore: sync .mokogitea/workflows/repo-health.yml from moko-platform [skip ci] 2026-06-03 03:10:44 +00:00
jmiller 883f11cbf3 chore: add .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-02 23:47:19 +00:00
jmiller eab88e8c8b chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-02 21:51:37 +00:00
Moko Consulting b402fa6fc4 chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:29:25 +00:00
Moko Consulting a3b4aa2015 chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:29:24 +00:00
Moko Consulting bb8f4dfa71 chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:29:23 +00:00
Moko Consulting d01b1e1675 chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:58 +00:00
Moko Consulting 8a4597552c chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:58 +00:00
Moko Consulting 4621eb5e3a chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:57 +00:00
gitea-actions[bot] 3b85985512 chore(ci): remove update-server.yml for update server migration [skip ci] 2026-05-31 03:41:13 +00:00
gitea-actions[bot] 0c12ccd506 chore(ci): remove cascade-dev.yml for update server migration [skip ci] 2026-05-31 03:41:12 +00:00
gitea-actions[bot] 5bad7a306d chore(ci): remove auto-bump.yml for update server migration [skip ci] 2026-05-31 03:41:10 +00:00
gitea-actions[bot] 50068d2894 chore(ci): remove pre-release.yml for update server migration [skip ci] 2026-05-31 03:41:09 +00:00
gitea-actions[bot] c1f412d5c7 chore(ci): remove auto-release.yml for update server migration [skip ci] 2026-05-31 03:41:07 +00:00
jmiller e2e80f0f00 chore: sync .mokogitea/workflows/cascade-dev.yml from moko-platform [skip ci] 2026-05-31 01:45:27 +00:00
jmiller 5095df4a18 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-05-30 16:03:16 +00:00
jmiller 2a36270a44 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-30 15:04:03 +00:00
jmiller c2804a4674 chore: sync .mokogitea/workflows/auto-bump.yml from moko-platform [skip ci] 2026-05-30 15:01:36 +00:00
jmiller 9c0887f69a chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-30 01:16:33 +00:00
jmiller 753ef94d78 chore: sync .mokogitea/workflows/auto-bump.yml from moko-platform [skip ci] 2026-05-29 10:32:01 +00:00
jmiller aca86af9ec chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-28 20:50:48 +00:00
jmiller a5d89cdd14 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-28 20:46:02 +00:00
jmiller 969f7a09e2 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-28 20:27:56 +00:00
jmiller e599140f89 chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-05-28 20:08:59 +00:00
jmiller d86dd19709 chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-28 20:05:40 +00:00
jmiller 70158b81ae chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-28 20:02:20 +00:00
Moko Consulting 7b29e79aff fix(workflows): rename remaining old secrets in repo-specific workflows [skip bump]
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:40:59 -05:00
Moko Consulting 4ef0e4fe84 fix(workflows): GITHUB_TOKEN→GH_MIRROR_TOKEN (reserved name) [skip bump]
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:34:30 -05:00
Moko Consulting 55f459c25b chore(workflows): sync all universal workflows from moko-platform [skip bump]
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:25:04 -05:00
Moko Consulting 559351332c refactor(workflows): rename secrets MOKOGITEA_TOKEN/GITHUB_TOKEN, use x-access-token [skip bump]
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:23:26 -05:00
Moko Consulting 140033dfe7 fix(workflows): proper suffix handling — use version_set_platform instead of sed [skip bump]
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:14:57 -05:00
Moko Consulting 617a408d0f chore: trigger update-server workflow for version suffix [skip bump]
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 14:02:30 -05:00
Moko Consulting b29fb85ccf feat(workflows): append stability suffix to manifest versions [skip bump]
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-28 13:41:07 -05:00
jmiller 8731306d7a docs: update CHANGELOG with infrastructure changes [skip ci] 2026-05-27 05:28:42 +00:00
jmiller 80f72ccf93 docs: update CHANGELOG with infrastructure changes [skip ci] 2026-05-27 04:52:09 +00:00
jmiller e60560f3c4 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:50:56 +00:00
jmiller 53c996bb57 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:49:44 +00:00
jmiller 3b00949e94 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:48:31 +00:00
jmiller e8fc9a892c chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:37:00 +00:00
jmiller dca6a8b283 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:35:38 +00:00
jmiller 047560e2eb chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:25:15 +00:00
jmiller 9a4b921323 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:23:58 +00:00
jmiller 96b8fefbb1 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:13:21 +00:00
jmiller 72165dccff chore(ci): add auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:12:09 +00:00
jmiller 3a17f3c0e4 fix(ci): use release_package.php for Joomla package builds [skip ci] 2026-05-26 19:54:32 +00:00
jmiller 0d41fe9644 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 19:34:39 +00:00
jmiller fd6647f7ae chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 19:34:38 +00:00
jmiller e391676fa8 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 17:36:23 +00:00
jmiller e6fbd1391b chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 17:34:50 +00:00
jmiller 5905908d62 sync: update-server.yml with updates.xml integrity check [skip ci] 2026-05-26 04:47:45 +00:00
jmiller b378d19c29 fix(updates): broaden targetplatform to match Joomla 5.x and 6.x [skip ci] 2026-05-26 03:43:00 +00:00
jmiller 635b896879 Merge pull request 'chore: cascade main → dev (2fd4772) [skip ci]' (#16) from main into dev
chore: cascade main → dev [skip ci]
2026-05-24 22:58:49 +00:00
jmiller 9b12979a60 Merge pull request 'chore: merge dev into main [skip ci]' (#14) from dev into main
chore: cascade main → dev [skip ci]
2026-05-24 22:57:54 +00:00
jmiller 2fd4772c06 Add RC pre-release trigger to CI workflow
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 6s
Generic: Repo Health / Release configuration (push) Failing after 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Repository health (push) Failing after 5s
Automatically triggers a release-candidate build when CI lint+tests
pass on a pull request.

Authored-by: Moko Consulting
2026-05-24 22:55:28 +00:00
jmiller 26a85f332d Add RC pre-release trigger to PR check workflow
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Release configuration (push) Failing after 4s
Generic: Repo Health / Scripts governance (push) Successful in 7s
Generic: Repo Health / Repository health (push) Failing after 7s
Automatically triggers a release-candidate build when a PR passes
branch policy and validation checks.

Authored-by: Moko Consulting
2026-05-24 22:54:38 +00:00
Jonathan Miller 3f57a3c7aa chore: cascade main -> dev, resolve conflicts [skip ci] 2026-05-24 14:49:14 -05:00
Moko Consulting dc2e0d42f7 chore: update CHANGELOG for deploy workflow removal
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Release configuration (push) Failing after 5s
Generic: Repo Health / Scripts governance (push) Successful in 4s
Generic: Repo Health / Repository health (push) Failing after 5s
2026-05-24 04:09:56 +00:00
jmiller d41ff35074 chore: remove deploy workflow — switching to Joomla update server method
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Release configuration (push) Successful in 4s
Generic: Repo Health / Scripts governance (push) Successful in 4s
Generic: Repo Health / Repository health (push) Failing after 4s
2026-05-24 03:44:11 +00:00
jmiller bebd55d874 fix(ci): add branch output to auto-release [skip ci] 2026-05-23 19:47:30 +00:00
jmiller 68ac2d40ef fix(ci): pre-release php-curl + continue-on-error + CLI updates.xml [skip ci] 2026-05-22 03:30:53 +00:00
jmiller 0df24b619a refactor(ci): sync auto-release.yml — CLI-based workflow [skip ci] 2026-05-22 02:55:54 +00:00
jmiller d60f8c06fc refactor(ci): pre-release uses CLI tools [skip ci] 2026-05-22 02:49:14 +00:00
jmiller f22fc7312a fix(ci): sync pre-release.yml — CLI-based updates.xml sync [skip ci] 2026-05-22 02:39:45 +00:00
jmiller acb6622737 fix(ci): sync pre-release.yml — updates.xml API sync (#34) [skip ci] 2026-05-22 02:35:30 +00:00
jmiller 7c796ec2a8 chore: sync security-audit.yml from moko-platform [skip ci] 2026-05-21 22:25:54 +00:00
jmiller b434bb1ec3 chore: sync repo-health.yml from moko-platform [skip ci] 2026-05-21 22:25:53 +00:00
jmiller 6574679c68 chore: sync pre-release.yml from moko-platform [skip ci] 2026-05-21 22:25:52 +00:00
jmiller 109e7cbb1a chore: sync pr-check.yml from moko-platform [skip ci] 2026-05-21 22:25:51 +00:00
jmiller ee38a547ed chore: sync notify.yml from moko-platform [skip ci] 2026-05-21 22:25:50 +00:00
jmiller 8ac5089bc0 chore: sync gitleaks.yml from moko-platform [skip ci] 2026-05-21 22:25:49 +00:00
jmiller 826b33001e chore: sync deploy-manual.yml from moko-platform [skip ci] 2026-05-21 22:25:48 +00:00
jmiller 5ebb08cd69 chore: sync cleanup.yml from moko-platform [skip ci] 2026-05-21 22:25:48 +00:00
jmiller b862a8befe chore: sync cascade-dev.yml from moko-platform [skip ci] 2026-05-21 22:25:47 +00:00
jmiller 5b1ec76936 chore: sync auto-release.yml from moko-platform [skip ci] 2026-05-21 22:25:46 +00:00
Jonathan Miller 45cc7d3080 chore(ci): use manifest.xml for platform detection, remove .moko-platform
Authored-by: Moko Consulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 17:19:05 -05:00
Jonathan Miller 2ec6a5f6b0 chore(ci): resolve merge — .gitea to .mokogitea migration complete
Authored-by: Moko Consulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 15:59:03 -05:00
Jonathan Miller 31efe86b1e chore(ci): migrate .gitea to .mokogitea and update workflows
Synced release workflows from Template-Joomla with package type support.

Authored-by: Moko Consulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 15:57:02 -05:00
Jonathan Miller d2f774db48 chore(ci): update release workflows with package type support
Synced from Template-Joomla: pre-release.yml and auto-release.yml now
handle Joomla package extensions (type="package" with sub-extensions).

Authored-by: Moko Consulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 15:52:55 -05:00
jmiller 44035da711 chore: update CLAUDE.md to reference .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 20:08:52 +00:00
jmiller 69705efd62 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:31 +00:00
jmiller a03538f1ed chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:30 +00:00
jmiller cb1e244e10 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:30 +00:00
jmiller e26af481d0 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:29 +00:00
jmiller 05dd2398f9 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:29 +00:00
jmiller c231dd5010 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:28 +00:00
jmiller e9c4e69d9b chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:28 +00:00
jmiller d3e4c98e8d chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:27 +00:00
jmiller e3ff8cfd93 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:27 +00:00
jmiller e3a87ceea2 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:26 +00:00
jmiller a4871571b0 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:26 +00:00
jmiller bf0d0a256a chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:25 +00:00
jmiller 1f8cb89ba2 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:24 +00:00
jmiller 571a2ceaba chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:24 +00:00
jmiller afeda847a0 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:23 +00:00
jmiller f8022553e1 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:23 +00:00
jmiller 975ec8b393 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:22 +00:00
jmiller 0a360b74c3 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:22 +00:00
jmiller 075a656ffb chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:21 +00:00
jmiller c6d2493006 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:21 +00:00
jmiller 69eeb1274e chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:20 +00:00
jmiller 7fb94c7399 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:20 +00:00
jmiller c2e5d4209f chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:19 +00:00
jmiller 533a0e81d9 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:19 +00:00
jmiller c3a475bddb chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:18 +00:00
jmiller 239468559d chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:17 +00:00
jmiller 8f4744382a chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:17 +00:00
jmiller e63228e062 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:16 +00:00
jmiller 37e3cf24ed chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:16 +00:00
jmiller 807ff35bc7 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:15 +00:00
jmiller 21dd88a51f chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:15 +00:00
jmiller 5397d36ce4 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:14 +00:00
jmiller 34887c4cd0 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:14 +00:00
jmiller 16c57fb5fa chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:13 +00:00
jmiller f7b4bb30f6 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:13 +00:00
jmiller 4b2e6764c3 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:12 +00:00
jmiller 94a81d0bea chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:12 +00:00
jmiller 581d95dea5 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:11 +00:00
jmiller 73bb39ae5b chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:11 +00:00
jmiller 5b299d19f8 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:10 +00:00
jmiller 2cc204d288 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:10 +00:00
jmiller ada3555f4b chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:09 +00:00
jmiller f4c1abf688 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:08 +00:00
jmiller 68da1b95b4 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:08 +00:00
jmiller dff481a53d chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:07 +00:00
jmiller 333a16b8a3 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:07 +00:00
jmiller ac387ef04a chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:06 +00:00
jmiller 5ee0b9e516 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:06 +00:00
jmiller a3bf34a31d chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:05 +00:00
jmiller ad6ac42f9f chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:05 +00:00
jmiller 21e91364ec chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:04 +00:00
jmiller 176176a191 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:04 +00:00
jmiller aa0a15fbd9 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:03 +00:00
jmiller 0bb0269a3a chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:03 +00:00
jmiller 93595173f4 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:02 +00:00
jmiller 25a72151bf chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:02 +00:00
jmiller 5ae97e91df chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:01 +00:00
jmiller c1cf9fffe2 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:01 +00:00
jmiller 8d0a80d82e chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:00 +00:00
jmiller 8b30eeca51 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:54:00 +00:00
jmiller 461a05b3a6 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:59 +00:00
jmiller 3588242ac5 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:59 +00:00
jmiller 96d1f077ec chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:58 +00:00
jmiller 3137dd99e1 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:57 +00:00
jmiller ab98c994c1 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:57 +00:00
jmiller 75b76502f8 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:57 +00:00
jmiller 571c75ffaf chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:56 +00:00
jmiller db52cc84d8 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:56 +00:00
jmiller f0269e1122 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:55 +00:00
jmiller f649659f98 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:54 +00:00
jmiller 00e61dbf56 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:54 +00:00
jmiller 84c89f1ed2 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:53 +00:00
jmiller 8653943794 chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:53 +00:00
jmiller da4cc0fdcd chore: rename .gitea/ to .mokogitea/ [skip ci]
Authored-by: Moko Consulting
2026-05-21 16:53:52 +00:00
jmiller 572ade05f6 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:39 +00:00
jmiller 7a7282571b chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:38 +00:00
jmiller b879d6260f chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:36 +00:00
jmiller 646a5aede7 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:35 +00:00
jmiller 9d8a738480 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:33 +00:00
jmiller 5f227bf719 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:32 +00:00
jmiller e5ef2623f7 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:31 +00:00
jmiller 3a31d84a86 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:29 +00:00
jmiller d8a74ffa69 chore: sync issue templates from template repo [skip ci] 2026-05-20 00:33:28 +00:00
jmiller 2eda6cd6e7 chore: rename Setup step to moko-platform [skip ci] 2026-05-16 22:19:23 +00:00
jmiller 7d1ba20882 chore: rename Setup step to moko-platform [skip ci] 2026-05-16 22:19:22 +00:00
jmiller b7c53cdfaa chore: add manifest.xml [skip ci] 2026-05-16 19:21:02 +00:00
jmiller f95e678ebf feat(ci): deploy auto-release to dev [skip ci] 2026-05-16 18:58:36 +00:00
jmiller 1a1399f6b8 feat(ci): CB plugin detection + platform auto-sense + remove GitHub mirror [skip ci] 2026-05-16 18:57:56 +00:00
jmiller 532c217051 chore: remove .mokogitea/workflows/update-server.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:15 +00:00
jmiller f583d68a3c chore: move .mokogitea/workflows/update-server.yml to .gitea/workflows/update-server.yml [skip ci] 2026-05-16 18:38:15 +00:00
jmiller 146489a9ae chore: remove .mokogitea/workflows/security-audit.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:14 +00:00
jmiller 8f96efda9c chore: move .mokogitea/workflows/security-audit.yml to .gitea/workflows/security-audit.yml [skip ci] 2026-05-16 18:38:14 +00:00
jmiller 67c054ebe1 chore: remove .mokogitea/workflows/repo-health.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:13 +00:00
jmiller 114b00f05d chore: move .mokogitea/workflows/repo-health.yml to .gitea/workflows/repo-health.yml [skip ci] 2026-05-16 18:38:13 +00:00
jmiller ee3d5693bd chore: remove .mokogitea/workflows/pre-release.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:13 +00:00
jmiller 3991021883 chore: move .mokogitea/workflows/pre-release.yml to .gitea/workflows/pre-release.yml [skip ci] 2026-05-16 18:38:12 +00:00
jmiller 19d647005e chore: remove .mokogitea/workflows/pr-check.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:12 +00:00
jmiller 48e7328038 chore: move .mokogitea/workflows/pr-check.yml to .gitea/workflows/pr-check.yml [skip ci] 2026-05-16 18:38:11 +00:00
jmiller fb2bfaa282 chore: remove .mokogitea/workflows/notify.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:11 +00:00
jmiller 45d21f1b5e chore: move .mokogitea/workflows/notify.yml to .gitea/workflows/notify.yml [skip ci] 2026-05-16 18:38:10 +00:00
jmiller 507189bee9 chore: remove .mokogitea/workflows/gitleaks.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:10 +00:00
jmiller dcac514773 chore: move .mokogitea/workflows/gitleaks.yml to .gitea/workflows/gitleaks.yml [skip ci] 2026-05-16 18:38:09 +00:00
jmiller 4988510a09 chore: remove .mokogitea/workflows/deploy-manual.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:09 +00:00
jmiller 608b71042c chore: move .mokogitea/workflows/deploy-manual.yml to .gitea/workflows/deploy-manual.yml [skip ci] 2026-05-16 18:38:09 +00:00
jmiller 6f0beb9763 chore: remove .mokogitea/workflows/cleanup.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:08 +00:00
jmiller ee3ba5a8eb chore: move .mokogitea/workflows/cleanup.yml to .gitea/workflows/cleanup.yml [skip ci] 2026-05-16 18:38:08 +00:00
jmiller faa0a40024 chore: remove .mokogitea/workflows/ci-joomla.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:07 +00:00
jmiller efeabc5cde chore: move .mokogitea/workflows/ci-joomla.yml to .gitea/workflows/ci-joomla.yml [skip ci] 2026-05-16 18:38:07 +00:00
jmiller 8694ab1b81 chore: remove .mokogitea/workflows/cascade-dev.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:06 +00:00
jmiller 1e39df8af5 chore: move .mokogitea/workflows/cascade-dev.yml to .gitea/workflows/cascade-dev.yml [skip ci] 2026-05-16 18:38:06 +00:00
jmiller fb91ec85fc chore: remove .mokogitea/workflows/auto-release.yml (exists in .gitea/) [skip ci] 2026-05-16 18:38:05 +00:00
jmiller c323fd3f07 chore: remove .mokogitea/update-server.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:05 +00:00
jmiller 5067f27b87 chore: move .mokogitea/update-server.yml to .gitea/update-server.yml [skip ci] 2026-05-16 18:38:04 +00:00
jmiller 833cf5b544 chore: remove .mokogitea/sync-roadmap-wiki.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:04 +00:00
jmiller 1b86674b26 chore: move .mokogitea/sync-roadmap-wiki.yml to .gitea/sync-roadmap-wiki.yml [skip ci] 2026-05-16 18:38:04 +00:00
jmiller 0fd2d2ba1b chore: remove .mokogitea/security-audit.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:03 +00:00
jmiller 8b3a238c48 chore: move .mokogitea/security-audit.yml to .gitea/security-audit.yml [skip ci] 2026-05-16 18:38:03 +00:00
jmiller 236669e8bd chore: remove .mokogitea/repo-health.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:02 +00:00
jmiller 60ac9c807c chore: move .mokogitea/repo-health.yml to .gitea/repo-health.yml [skip ci] 2026-05-16 18:38:02 +00:00
jmiller e3eca2296a chore: remove .mokogitea/pre-release.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:01 +00:00
jmiller 4eb64ca890 chore: move .mokogitea/pre-release.yml to .gitea/pre-release.yml [skip ci] 2026-05-16 18:38:01 +00:00
jmiller b783af913c chore: remove .mokogitea/pr-check.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:00 +00:00
jmiller 21a184576a chore: move .mokogitea/pr-check.yml to .gitea/pr-check.yml [skip ci] 2026-05-16 18:38:00 +00:00
jmiller 6af7479542 chore: remove .mokogitea/pr-branch-check.yml (moved to .gitea/) [skip ci] 2026-05-16 18:38:00 +00:00
jmiller cfee137685 chore: move .mokogitea/pr-branch-check.yml to .gitea/pr-branch-check.yml [skip ci] 2026-05-16 18:37:59 +00:00
jmiller b3665dfe3b chore: remove .mokogitea/notify.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:59 +00:00
jmiller 5175b30c1c chore: move .mokogitea/notify.yml to .gitea/notify.yml [skip ci] 2026-05-16 18:37:58 +00:00
jmiller cfe552dd64 chore: remove .mokogitea/gitleaks.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:58 +00:00
jmiller 26f80aafbc chore: move .mokogitea/gitleaks.yml to .gitea/gitleaks.yml [skip ci] 2026-05-16 18:37:58 +00:00
jmiller 3c72252ab5 chore: remove .mokogitea/deploy-manual.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:57 +00:00
jmiller 61a98ee393 chore: move .mokogitea/deploy-manual.yml to .gitea/deploy-manual.yml [skip ci] 2026-05-16 18:37:57 +00:00
jmiller c97ca5dca5 chore: remove .mokogitea/cleanup.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:56 +00:00
jmiller ea42677b61 chore: move .mokogitea/cleanup.yml to .gitea/cleanup.yml [skip ci] 2026-05-16 18:37:56 +00:00
jmiller 9cf7f89b64 chore: remove .mokogitea/ci-joomla.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:55 +00:00
jmiller 7290d6e19b chore: move .mokogitea/ci-joomla.yml to .gitea/ci-joomla.yml [skip ci] 2026-05-16 18:37:55 +00:00
jmiller 64cc1186a7 chore: remove .mokogitea/cascade-dev.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:54 +00:00
jmiller 0d3341558a chore: move .mokogitea/cascade-dev.yml to .gitea/cascade-dev.yml [skip ci] 2026-05-16 18:37:54 +00:00
jmiller 5513961c91 chore: remove .mokogitea/auto-release.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:54 +00:00
jmiller fe800ff6c0 chore: move .mokogitea/auto-release.yml to .gitea/auto-release.yml [skip ci] 2026-05-16 18:37:53 +00:00
jmiller 7f8930d388 chore: remove .mokogitea/ISSUE_TEMPLATE/version.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:53 +00:00
jmiller 850cf197f8 chore: move .mokogitea/ISSUE_TEMPLATE/version.md to .gitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-16 18:37:52 +00:00
jmiller c8e5cda9b4 chore: remove .mokogitea/ISSUE_TEMPLATE/security.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:52 +00:00
jmiller 255ab24bb7 chore: move .mokogitea/ISSUE_TEMPLATE/security.md to .gitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-16 18:37:51 +00:00
jmiller de3da37e61 chore: remove .mokogitea/ISSUE_TEMPLATE/rfc.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:51 +00:00
jmiller e1a7e1ab81 chore: move .mokogitea/ISSUE_TEMPLATE/rfc.md to .gitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-16 18:37:51 +00:00
jmiller 57bced4bd8 chore: remove .mokogitea/ISSUE_TEMPLATE/question.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:50 +00:00
jmiller d3242702e1 chore: move .mokogitea/ISSUE_TEMPLATE/question.md to .gitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-16 18:37:50 +00:00
jmiller 165c5d3722 chore: remove .mokogitea/ISSUE_TEMPLATE/joomla_issue.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:49 +00:00
jmiller 7d75015e1c chore: move .mokogitea/ISSUE_TEMPLATE/joomla_issue.md to .gitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-16 18:37:49 +00:00
jmiller d9ea63bf0a chore: remove .mokogitea/ISSUE_TEMPLATE/feature_request.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:49 +00:00
jmiller 5a1ca2a554 chore: move .mokogitea/ISSUE_TEMPLATE/feature_request.md to .gitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-16 18:37:48 +00:00
jmiller 58a6c7e4ec chore: remove .mokogitea/ISSUE_TEMPLATE/documentation.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:48 +00:00
jmiller 7b1d49c02b chore: move .mokogitea/ISSUE_TEMPLATE/documentation.md to .gitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-16 18:37:47 +00:00
jmiller 625124f5c7 chore: remove .mokogitea/ISSUE_TEMPLATE/config.yml (moved to .gitea/) [skip ci] 2026-05-16 18:37:47 +00:00
jmiller 4cb64e3d55 chore: move .mokogitea/ISSUE_TEMPLATE/config.yml to .gitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-16 18:37:46 +00:00
jmiller e9d5246c06 chore: remove .mokogitea/ISSUE_TEMPLATE/bug_report.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:46 +00:00
jmiller df504bb6dd chore: move .mokogitea/ISSUE_TEMPLATE/bug_report.md to .gitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-16 18:37:46 +00:00
jmiller 071ea4757f chore: remove .mokogitea/ISSUE_TEMPLATE/adr.md (moved to .gitea/) [skip ci] 2026-05-16 18:37:45 +00:00
jmiller 569b9a8631 chore: move .mokogitea/ISSUE_TEMPLATE/adr.md to .gitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-16 18:37:45 +00:00
jmiller b85d10257d fix(ci): platform auto-sense, .mokogitea precedence, remove GitHub mirror [skip ci] 2026-05-16 18:27:14 +00:00
jmiller 5acc86b2e1 fix(ci): auto-release on PR merge + dispatch, bump dev after [skip ci] 2026-05-16 18:00:39 +00:00
jmiller 6300095f54 fix(ci): restrict auto-release to workflow_dispatch only [skip ci] 2026-05-16 17:57:21 +00:00
jmiller 76411fc6e6 fix(ci): deploy auto-release with new version bump protocol [skip ci] 2026-05-16 17:41:45 +00:00
Jonathan Miller 4ab1d2ba61 feat(ci): add changelog gate to PR checks [skip ci]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 09:09:17 -05:00
Jonathan Miller ef648d0582 chore(ci): version bump targets dev branch instead of main [skip ci]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 09:04:18 -05:00
jmiller 511c98a2b4 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:16:33 +00:00
jmiller 04d874bf55 chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:16:33 +00:00
jmiller beabd97294 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:16:32 +00:00
jmiller 59f189c9dd chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:16:32 +00:00
jmiller e43bd1db82 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:16:32 +00:00
jmiller 2f8d5635f6 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:16:31 +00:00
jmiller 1e05f60cd8 chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:16:31 +00:00
jmiller ac0b3eb233 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:16:30 +00:00
jmiller ab4e31d7c8 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:16:30 +00:00
jmiller dc4342142b chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:16:30 +00:00
jmiller ec3b3e44a3 chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:16:29 +00:00
jmiller b37c3c6c32 chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:16:29 +00:00
jmiller 481388e793 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:16:28 +00:00
jmiller 490c0769b5 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:16:28 +00:00
jmiller 5206a96ae4 chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:16:28 +00:00
jmiller cd3f0ec5da chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:16:27 +00:00
jmiller 08f5bc0bf9 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:16:27 +00:00
jmiller 5efbae0994 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:16:27 +00:00
jmiller 5ae3b07e16 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:16:26 +00:00
jmiller dce6ba7748 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:16:26 +00:00
jmiller b61c2a75d9 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:16:25 +00:00
jmiller f6a6bc39eb chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:16:25 +00:00
jmiller 6081964659 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:16:24 +00:00
jmiller 28a1f7d0d2 chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:16:24 +00:00
jmiller 746222d7ea chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:16:23 +00:00
jmiller f529968422 chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:16:23 +00:00
jmiller 17aaad19b1 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:16:23 +00:00
jmiller d7a677c7f7 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:16:22 +00:00
jmiller 074f9eebd4 chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:16:22 +00:00
jmiller 7098976087 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:16:21 +00:00
jmiller e5b05a4537 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:16:21 +00:00
jmiller 649e836466 chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:16:21 +00:00
jmiller d646372198 chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:16:20 +00:00
jmiller 5b0e8e39bd chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:16:20 +00:00
jmiller 2c490887a4 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:16:19 +00:00
jmiller 486ed44198 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:16:19 +00:00
jmiller 8f7cf59d4b chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:16:19 +00:00
jmiller f3940bb772 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:16:18 +00:00
jmiller 94244f3a5f chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:16:18 +00:00
jmiller 39ff440973 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:16:18 +00:00
jmiller 391f5f4260 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:16:17 +00:00
jmiller 8583ecf003 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:16:17 +00:00
jmiller fdd07d7666 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:16:16 +00:00
jmiller 0c80a9d2db chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:16:16 +00:00
jmiller 152a8b5550 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:11:31 +00:00
jmiller f80f3fc663 chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:11:31 +00:00
jmiller 80dbfd7bf1 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:11:31 +00:00
jmiller fa615788d5 chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:11:30 +00:00
jmiller f764b5553a chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:11:30 +00:00
jmiller cda7ca51e5 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:11:29 +00:00
jmiller d7d3f16426 chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:11:29 +00:00
jmiller 946368ff91 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:11:29 +00:00
jmiller a22f4d8734 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:11:28 +00:00
jmiller 398ff8721d chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:11:28 +00:00
jmiller 1f6fcffd90 chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:11:27 +00:00
jmiller 0dab58606b chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:11:27 +00:00
jmiller 2dc044303c chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:11:27 +00:00
jmiller c800438ab5 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:11:26 +00:00
jmiller 371e97de26 chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:11:26 +00:00
jmiller 9122a144d4 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:11:26 +00:00
jmiller 530064b656 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:11:25 +00:00
jmiller d9d5a5ec63 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:11:25 +00:00
jmiller 5b526fef26 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:11:24 +00:00
jmiller 6d76b4addc chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:11:24 +00:00
jmiller b0dadfd48f chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:11:24 +00:00
jmiller c8b4772341 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:11:23 +00:00
jmiller 31bd7cd606 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:11:23 +00:00
jmiller 870e2c071b chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:11:22 +00:00
jmiller 584cd60db8 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:11:22 +00:00
jmiller feea10173a chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:11:21 +00:00
jmiller ea0b9e462a chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:11:21 +00:00
jmiller a128cdde42 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:11:21 +00:00
jmiller fe51b7638c chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:11:20 +00:00
jmiller c90c015814 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:11:20 +00:00
jmiller 87d59df3e1 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:11:19 +00:00
jmiller cc92296fd5 chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:11:19 +00:00
jmiller 8f6c66fd2c chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:11:18 +00:00
jmiller 2bd95a8914 chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:11:18 +00:00
jmiller 2af99417f4 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:11:18 +00:00
jmiller 4411949b49 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:11:17 +00:00
jmiller 1bad61d155 chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:11:17 +00:00
jmiller 3582ca6336 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:11:17 +00:00
jmiller 2e70a2f2ad chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:11:16 +00:00
jmiller c1fbc1905e chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:11:16 +00:00
jmiller 50d8af7bd7 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:11:15 +00:00
jmiller 7a6f2e0125 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:11:15 +00:00
jmiller 09c0e84936 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:11:15 +00:00
jmiller 983f32631d chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:11:14 +00:00
jmiller cc798c0fd3 chore: remove .mokogitea/.moko-platform [skip ci] 2026-05-12 19:08:46 +00:00
jmiller f8ea5f7698 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:08:45 +00:00
jmiller 65678e7a7b chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:08:45 +00:00
jmiller 432e520544 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:08:44 +00:00
jmiller c437e671ed chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:08:44 +00:00
jmiller bf6b0a2ab0 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:08:44 +00:00
jmiller b2400c4ef7 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:08:43 +00:00
jmiller 9c04d6c84a chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:08:43 +00:00
jmiller ba929326ff chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:08:42 +00:00
jmiller 95e4f89256 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:08:42 +00:00
jmiller 9052778091 chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:08:42 +00:00
jmiller f5d5fef1bb chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:08:41 +00:00
jmiller ca6998c9f6 chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:08:41 +00:00
jmiller 9f55fe2264 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:08:40 +00:00
jmiller 3862eff934 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:08:40 +00:00
jmiller b40a649e8b chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:08:40 +00:00
jmiller a35f6a839b chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:08:39 +00:00
jmiller 52c18f1b90 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:08:39 +00:00
jmiller 7ef7298cdd chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:08:39 +00:00
jmiller 730bad5f77 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:08:38 +00:00
jmiller 78873d6187 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:08:38 +00:00
jmiller 7cd2a817fa chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:08:37 +00:00
jmiller 2188eca5d5 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:08:37 +00:00
jmiller d1a8d99585 chore: remove .mokogitea/.moko-platform [skip ci] 2026-05-12 19:08:36 +00:00
jmiller 07f3b7dea8 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:08:36 +00:00
jmiller 9d376f8585 chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:08:36 +00:00
jmiller c62cf89d46 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:08:35 +00:00
jmiller bca73294a3 chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:08:35 +00:00
jmiller e3f7a5e2a1 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:08:34 +00:00
jmiller aad25c4bc4 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:08:34 +00:00
jmiller 0d3d0f8739 chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:08:34 +00:00
jmiller 612314e68a chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:08:33 +00:00
jmiller 6b81591e37 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:08:33 +00:00
jmiller edb3163bfb chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:08:32 +00:00
jmiller 9e5ef23a4d chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:08:32 +00:00
jmiller 4bbdf09374 chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:08:32 +00:00
jmiller 74f137cd67 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:08:31 +00:00
jmiller a092a47264 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:08:31 +00:00
jmiller d4d5f202c2 chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:08:30 +00:00
jmiller 555d2768cf chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:08:30 +00:00
jmiller e126014418 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:08:30 +00:00
jmiller 0c0ed62a86 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:08:29 +00:00
jmiller 4433106729 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:08:29 +00:00
jmiller 0a1b3cad4f chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:08:29 +00:00
jmiller e912310752 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:08:28 +00:00
jmiller b9923c5fa6 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:08:28 +00:00
jmiller 196cbd580c chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:47:05 +00:00
jmiller ac7494b522 chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:47:04 +00:00
jmiller 68680f9239 chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:47:04 +00:00
jmiller e33fda2ccf chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:47:03 +00:00
jmiller 7ad5e1970b chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:47:03 +00:00
jmiller ec25a87b7a chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:47:03 +00:00
jmiller 496e18d57e chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:47:02 +00:00
jmiller 1fa7968dd9 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:47:02 +00:00
jmiller 6e6a51f03b chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:47:02 +00:00
jmiller b831b8f5af chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:47:01 +00:00
jmiller ac1e7d71be chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:47:01 +00:00
jmiller c654e00a84 chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:47:00 +00:00
jmiller cdbbf4d796 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:47:00 +00:00
jmiller b0b4e3a10d chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:47:00 +00:00
jmiller fb99c2e6d1 chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:46:59 +00:00
jmiller 86a42278bf chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:46:59 +00:00
jmiller ebaedd5ff7 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:46:59 +00:00
jmiller a8c30ae72a chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:46:58 +00:00
jmiller 5931fd5c28 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:46:58 +00:00
jmiller a57ea2f326 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:46:57 +00:00
jmiller 328b688b5a chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:46:57 +00:00
jmiller 7691cb8e0a chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:46:57 +00:00
jmiller 95eee47151 chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:46:56 +00:00
jmiller cff9acb79a chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:46:56 +00:00
jmiller 632bb1f1b5 chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:46:55 +00:00
jmiller 0e71dc25c7 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:46:55 +00:00
jmiller d180994dc3 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:46:55 +00:00
jmiller 73966f7da6 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:46:54 +00:00
jmiller 5a10145dec chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:46:54 +00:00
jmiller 5c586b1c61 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:46:53 +00:00
jmiller d52fb497ac chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:46:53 +00:00
jmiller a8a4cf7cd4 chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:46:53 +00:00
jmiller 36e8dbeb52 chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:46:52 +00:00
jmiller 3819a132f4 chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:46:52 +00:00
jmiller 5a74095ec7 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:46:51 +00:00
jmiller ace1603fce chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:46:51 +00:00
jmiller 5271cc3699 chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:46:51 +00:00
jmiller 695c85df5c chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:46:50 +00:00
jmiller 656073cfba chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:46:50 +00:00
jmiller 6c98b3c403 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:46:50 +00:00
jmiller 77a1f84f86 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:46:49 +00:00
jmiller d9b93ceec2 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:46:49 +00:00
jmiller acad220640 chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:46:48 +00:00
jmiller a57e060d65 chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:46:48 +00:00
jmiller dec5fdf040 chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:37:26 +00:00
jmiller aec4e04bf0 chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:37:25 +00:00
jmiller 3892f5523d chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:37:25 +00:00
jmiller 4c8a927585 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:37:25 +00:00
jmiller 45eacb425d chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:37:24 +00:00
jmiller 7924fd9248 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:37:24 +00:00
jmiller 3a6fa0393b chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:37:23 +00:00
jmiller 743447c3a9 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:37:23 +00:00
jmiller b07c4e57bf chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:37:22 +00:00
jmiller 1d332e958b chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:37:22 +00:00
jmiller 96e03775ae chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:37:22 +00:00
jmiller 5aa915b533 chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:37:21 +00:00
jmiller 6650eb3ef8 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:37:21 +00:00
jmiller 8441454d39 chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:37:20 +00:00
jmiller 2d536d9dfe chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:37:20 +00:00
jmiller 33c7fc225a chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:37:20 +00:00
jmiller 29a765b0d5 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:37:19 +00:00
jmiller 30947d8a43 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:37:19 +00:00
jmiller 713dc72a21 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:37:18 +00:00
jmiller ef289d2366 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:37:18 +00:00
jmiller e7bc3511f2 chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:37:18 +00:00
jmiller 5ced9404dc chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:37:17 +00:00
jmiller aad2e17eed chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:37:17 +00:00
jmiller 75720bb92a chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:37:16 +00:00
jmiller b2487dcafd chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:37:16 +00:00
jmiller 9617ec71f9 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:37:16 +00:00
jmiller 449b1cecb6 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:37:15 +00:00
jmiller 4ddb2c31d3 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:37:15 +00:00
jmiller afa5783396 chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:37:15 +00:00
jmiller 5b4c25ce4c chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:37:14 +00:00
jmiller 73b378a84f chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:37:14 +00:00
jmiller 96fc14668a chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:37:13 +00:00
jmiller ef1336c9df chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:37:13 +00:00
jmiller 1a6936ba0f chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:37:13 +00:00
jmiller cbead82256 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:37:12 +00:00
jmiller 2722e468f5 chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:37:12 +00:00
jmiller db0ddc0a3e chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:37:12 +00:00
jmiller 474e687e77 chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:37:11 +00:00
jmiller 188e94bf34 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:37:11 +00:00
jmiller 2237820a09 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:37:11 +00:00
jmiller c3aca21494 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:37:10 +00:00
jmiller 45e2deef52 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:37:10 +00:00
jmiller bfdbe96fee chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:37:09 +00:00
jmiller 40fa7ebdc6 chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:37:09 +00:00
jmiller 9744bf735c chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 05:31:11 +00:00
jmiller 2a1d26e92d chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 05:31:10 +00:00
jmiller f1cfce8f05 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 05:31:10 +00:00
jmiller ed6f771a1e chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 05:31:10 +00:00
jmiller 5dfc1c9476 chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 05:31:09 +00:00
jmiller b611aaa7e0 chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 05:31:09 +00:00
jmiller 0c3a11f524 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 05:31:09 +00:00
jmiller fa267ebcb9 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 05:31:08 +00:00
jmiller cce326f5c8 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 05:31:08 +00:00
jmiller 41d674001c chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 05:31:08 +00:00
jmiller 2bb6daa405 chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 05:31:07 +00:00
jmiller 53d2487fe8 chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 05:31:07 +00:00
jmiller 99a6a14964 chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 05:31:06 +00:00
jmiller b3c4d5102c chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 05:31:06 +00:00
jmiller f901431532 chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 05:31:06 +00:00
jmiller c9431fe31e chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 05:31:05 +00:00
jmiller 77e9449e75 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 05:31:05 +00:00
jmiller 648daeaa88 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 05:31:05 +00:00
jmiller fafcfe688c chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 05:31:04 +00:00
jmiller 7c544f1a9b chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 05:31:04 +00:00
jmiller ce494add2e chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 05:31:04 +00:00
jmiller 51527073ab chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 05:31:03 +00:00
jmiller 13fcc87e0f chore: sync .mokogitea/.moko-platform from template [skip ci] 2026-05-12 05:31:03 +00:00
jmiller 559c48363f chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 05:31:02 +00:00
jmiller c81e483366 chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 05:31:02 +00:00
jmiller fc92366acc chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 05:31:02 +00:00
jmiller 80922179de chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 05:31:01 +00:00
jmiller 70b28cdf0b chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 05:31:01 +00:00
jmiller 57d477149f chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 05:31:01 +00:00
jmiller 9b7f2a3514 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 05:31:00 +00:00
jmiller a975d3b7eb chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 05:31:00 +00:00
jmiller 58f536bffc chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 05:30:59 +00:00
jmiller 94eeb1dab1 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 05:30:59 +00:00
jmiller 1c776d3a0f chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 05:30:59 +00:00
jmiller 5f061c0e2f chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 05:30:58 +00:00
jmiller 30b12bd1de chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 05:30:58 +00:00
jmiller d95ffb02c0 chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 05:30:58 +00:00
jmiller eb9b7ac25d chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 05:30:57 +00:00
jmiller 8ede04c284 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 05:30:57 +00:00
jmiller b913b36232 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 05:30:57 +00:00
jmiller 21b1771380 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 05:30:56 +00:00
jmiller e24c21e123 chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 05:30:56 +00:00
jmiller 2bfd19fa63 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 05:30:56 +00:00
jmiller adc44004be chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 05:30:55 +00:00
jmiller 5000d38294 chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 05:30:55 +00:00
jmiller 51674d4b04 chore: sync .mokogitea/.moko-platform from template [skip ci] 2026-05-12 05:30:55 +00:00
jmiller 3f6a1d2278 chore: remove .gitea/workflows/update-server.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:08 +00:00
jmiller b61b9aac37 chore: move .gitea/workflows/update-server.yml to .mokogitea/update-server.yml [skip ci] 2026-05-12 04:58:08 +00:00
jmiller 7ed55a577f chore: remove .gitea/workflows/sync-roadmap-wiki.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:08 +00:00
jmiller 924fe39a7b chore: move .gitea/workflows/sync-roadmap-wiki.yml to .mokogitea/sync-roadmap-wiki.yml [skip ci] 2026-05-12 04:58:07 +00:00
jmiller 64d7b4a93c chore: remove .gitea/workflows/security-audit.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:07 +00:00
jmiller c7427325b7 chore: move .gitea/workflows/security-audit.yml to .mokogitea/security-audit.yml [skip ci] 2026-05-12 04:58:07 +00:00
jmiller 21f4e7c655 chore: remove .gitea/workflows/repo-health.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:06 +00:00
jmiller 978e88dfb1 chore: move .gitea/workflows/repo-health.yml to .mokogitea/repo-health.yml [skip ci] 2026-05-12 04:58:06 +00:00
jmiller decab5f80e chore: remove .gitea/workflows/pre-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:05 +00:00
jmiller 594c5ebb48 chore: move .gitea/workflows/pre-release.yml to .mokogitea/pre-release.yml [skip ci] 2026-05-12 04:58:05 +00:00
jmiller bbf89e9f61 chore: remove .gitea/workflows/pr-check.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:05 +00:00
jmiller e0dd66c1f7 chore: move .gitea/workflows/pr-check.yml to .mokogitea/pr-check.yml [skip ci] 2026-05-12 04:58:04 +00:00
jmiller a0f83db86e chore: remove .gitea/workflows/pr-branch-check.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:04 +00:00
jmiller afbcfc7f07 chore: move .gitea/workflows/pr-branch-check.yml to .mokogitea/pr-branch-check.yml [skip ci] 2026-05-12 04:58:04 +00:00
jmiller 8bd22a53e4 chore: remove .gitea/workflows/notify.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:03 +00:00
jmiller 69d9335ad6 chore: move .gitea/workflows/notify.yml to .mokogitea/notify.yml [skip ci] 2026-05-12 04:58:03 +00:00
jmiller 987e137db3 chore: remove .gitea/workflows/gitleaks.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:03 +00:00
jmiller 5a68945b96 chore: move .gitea/workflows/gitleaks.yml to .mokogitea/gitleaks.yml [skip ci] 2026-05-12 04:58:02 +00:00
jmiller 29f7ad8459 chore: remove .gitea/workflows/deploy-manual.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:02 +00:00
jmiller 90fee9905a chore: move .gitea/workflows/deploy-manual.yml to .mokogitea/deploy-manual.yml [skip ci] 2026-05-12 04:58:02 +00:00
jmiller d19c1efd40 chore: remove .gitea/workflows/cleanup.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:01 +00:00
jmiller 301f861431 chore: move .gitea/workflows/cleanup.yml to .mokogitea/cleanup.yml [skip ci] 2026-05-12 04:58:01 +00:00
jmiller 866b7c86df chore: remove .gitea/workflows/ci-joomla.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:00 +00:00
jmiller 25f7e7bfe5 chore: move .gitea/workflows/ci-joomla.yml to .mokogitea/ci-joomla.yml [skip ci] 2026-05-12 04:58:00 +00:00
jmiller 2eed2e88f3 chore: remove .gitea/workflows/cascade-dev.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:00 +00:00
jmiller 153ad6cc28 chore: move .gitea/workflows/cascade-dev.yml to .mokogitea/cascade-dev.yml [skip ci] 2026-05-12 04:57:59 +00:00
jmiller 994974e6c9 chore: remove .gitea/workflows/auto-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:59 +00:00
jmiller 24d866e2c0 chore: move .gitea/workflows/auto-release.yml to .mokogitea/auto-release.yml [skip ci] 2026-05-12 04:57:59 +00:00
jmiller 8bbe2cae21 chore: remove .gitea/.moko-platform (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:58 +00:00
jmiller 71997cd9fa chore: move .gitea/.moko-platform to .mokogitea/.moko-platform [skip ci] 2026-05-12 04:57:58 +00:00
jmiller 512faf4874 chore: remove .gitea/workflows/update-server.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:57 +00:00
jmiller ab3527ccbc chore: move .gitea/workflows/update-server.yml to .mokogitea/update-server.yml [skip ci] 2026-05-12 04:57:57 +00:00
jmiller 3c67fb586f chore: remove .gitea/workflows/sync-roadmap-wiki.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:56 +00:00
jmiller cf77d37c53 chore: move .gitea/workflows/sync-roadmap-wiki.yml to .mokogitea/sync-roadmap-wiki.yml [skip ci] 2026-05-12 04:57:56 +00:00
jmiller 06ad85e702 chore: remove .gitea/workflows/security-audit.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:56 +00:00
jmiller a633671fe4 chore: move .gitea/workflows/security-audit.yml to .mokogitea/security-audit.yml [skip ci] 2026-05-12 04:57:55 +00:00
jmiller 2f6965c0d9 chore: remove .gitea/workflows/repo-health.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:55 +00:00
jmiller 50d9dc1262 chore: move .gitea/workflows/repo-health.yml to .mokogitea/repo-health.yml [skip ci] 2026-05-12 04:57:55 +00:00
jmiller 5c9b18917c chore: remove .gitea/workflows/pre-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:54 +00:00
jmiller 9581a9327e chore: move .gitea/workflows/pre-release.yml to .mokogitea/pre-release.yml [skip ci] 2026-05-12 04:57:54 +00:00
jmiller 6cbf36872e chore: remove .gitea/workflows/pr-check.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:53 +00:00
jmiller 2b265fb21f chore: move .gitea/workflows/pr-check.yml to .mokogitea/pr-check.yml [skip ci] 2026-05-12 04:57:53 +00:00
jmiller 1a6f834a73 chore: remove .gitea/workflows/pr-branch-check.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:53 +00:00
jmiller a715f5ef9c chore: move .gitea/workflows/pr-branch-check.yml to .mokogitea/pr-branch-check.yml [skip ci] 2026-05-12 04:57:52 +00:00
jmiller 21f87c4b9c chore: remove .gitea/workflows/notify.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:52 +00:00
jmiller e7c594f608 chore: move .gitea/workflows/notify.yml to .mokogitea/notify.yml [skip ci] 2026-05-12 04:57:52 +00:00
jmiller eaad4ddde9 chore: remove .gitea/workflows/gitleaks.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:51 +00:00
jmiller 87b22a9c21 chore: move .gitea/workflows/gitleaks.yml to .mokogitea/gitleaks.yml [skip ci] 2026-05-12 04:57:51 +00:00
jmiller 869032a02f chore: remove .gitea/workflows/deploy-manual.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:51 +00:00
jmiller cc02beb68a chore: move .gitea/workflows/deploy-manual.yml to .mokogitea/deploy-manual.yml [skip ci] 2026-05-12 04:57:50 +00:00
jmiller 3ea20894e4 chore: remove .gitea/workflows/cleanup.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:50 +00:00
jmiller 395fee0ac3 chore: move .gitea/workflows/cleanup.yml to .mokogitea/cleanup.yml [skip ci] 2026-05-12 04:57:50 +00:00
jmiller 1a43531baf chore: remove .gitea/workflows/ci-joomla.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:49 +00:00
jmiller e5dd118b30 chore: move .gitea/workflows/ci-joomla.yml to .mokogitea/ci-joomla.yml [skip ci] 2026-05-12 04:57:49 +00:00
jmiller b3e9dce02d chore: remove .gitea/workflows/cascade-dev.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:49 +00:00
jmiller 9cb1aa9507 chore: move .gitea/workflows/cascade-dev.yml to .mokogitea/cascade-dev.yml [skip ci] 2026-05-12 04:57:48 +00:00
jmiller 51dcca076b chore: remove .gitea/workflows/auto-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:48 +00:00
jmiller 91b2ee2fd5 chore: move .gitea/workflows/auto-release.yml to .mokogitea/auto-release.yml [skip ci] 2026-05-12 04:57:47 +00:00
jmiller c3077e5509 chore: remove .gitea/.moko-platform (moved to .mokogitea/) [skip ci] 2026-05-12 04:57:47 +00:00
jmiller 65df448186 chore: move .gitea/.moko-platform to .mokogitea/.moko-platform [skip ci] 2026-05-12 04:57:47 +00:00
jmiller 15480eefb4 chore: sync pr-branch-check.yml from Template-Joomla [skip ci] 2026-05-11 21:28:24 +00:00
jmiller 16c0669db7 chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-05-11 21:23:22 +00:00
jmiller 55b6d29c87 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-05-11 21:23:20 +00:00
jmiller 29fd0d1456 chore: sync notify.yml from Template-Joomla [skip ci] 2026-05-11 21:23:16 +00:00
jmiller a431e8395e chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-05-11 21:23:09 +00:00
jmiller e985ea2e2b chore: sync repo-health.yml from Template-Joomla [skip ci] 2026-05-11 21:23:05 +00:00
jmiller bd7cebe762 chore: sync pre-release.yml from Template-Joomla [skip ci] 2026-05-11 21:23:04 +00:00
jmiller 2f9f4b562b chore: sync notify.yml from Template-Joomla [skip ci] 2026-05-11 21:23:00 +00:00
jmiller bdc073b285 chore: sync auto-release.yml from Template-Joomla [skip ci] 2026-05-11 21:22:54 +00:00
jmiller 672f4c423a chore: add PR branch policy check workflow [skip ci] 2026-05-11 17:15:38 +00:00
jmiller 2ed90b5116 chore: sync auto-release.yml with changelog+SHA+pretty-name fixes [skip ci] 2026-05-11 16:43:31 +00:00
jmiller da0a202e8b chore: sync auto-release.yml from MokoGalleryCalendar [skip ci] 2026-05-11 16:41:19 +00:00
jmiller 8d0bcf8378 chore: remove renovate.json [skip ci] 2026-05-10 19:57:27 +00:00
jmiller 009b5b7c3c chore: remove renovate.json [skip ci] 2026-05-10 19:57:27 +00:00
jmiller 061e8daa34 docs: add CLAUDE.md for Claude Code context [skip ci] 2026-05-10 19:55:23 +00:00
jmiller edf1e16fee chore: remove .claude/ from version control [skip ci] 2026-05-10 19:48:19 +00:00
jmiller 0c6657a2f2 Merge pull request 'chore: cascade main → dev (73691de) [skip ci]' (#13) from main into dev 2026-05-10 19:14:24 +00:00
jmiller 73691de227 docs: comprehensive README with full endpoint tables, examples, and query params
Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Repo Health / Access control (push) Successful in 0s
Repo Health / Release configuration (push) Successful in 2s
Repo Health / Scripts governance (push) Successful in 2s
Repo Health / Repository health (push) Failing after 3s
Repository Cleanup / Clean Merged Branches (push) Successful in 5s
Secret Scanning / Gitleaks Secret Scan (push) Failing after 12s
Sync Roadmap to Wiki / Generate Roadmap Wiki (push) Successful in 4s
Security Audit / Dependency Audit (push) Successful in 3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 19:14:19 +00:00
jmiller d65eee67fa Merge pull request 'chore: cascade main → dev (6e86799) [skip ci]' (#12) from main into dev 2026-05-10 18:44:23 +00:00
jmiller 6e86799bb0 docs: update README from wiki Home
Repo Health / Access control (push) Successful in 1s
Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Repo Health / Release configuration (push) Successful in 3s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
2026-05-10 18:39:22 +00:00
jmiller 7f121512b2 chore: sync updates.xml 03.02.00 [skip ci] 2026-05-10 00:34:11 +00:00
gitea-actions[bot] 2da34f3376 chore(release): ZIP + tar.gz for 03.02.00 [skip ci] 2026-05-10 00:34:11 +00:00
gitea-actions[bot] 53dd1a7afa chore(release): build 03.02.00 [skip ci] 2026-05-10 00:34:10 +00:00
gitea-actions[bot] 762c274bb5 chore(version): bump 03.01.00 → 03.02.00 (minor) [skip ci] 2026-05-10 00:34:08 +00:00
Jonathan Miller 9b982d1670 fix: correct ZIP filename in updates.xml download URLs [skip ci] 2026-05-09 17:36:29 -05:00
jmiller 212bff1c55 chore: sync updates.xml 03.01.00 [skip ci] 2026-05-09 22:14:55 +00:00
gitea-actions[bot] 31990695f3 chore(version): bump 03.00.01 → 03.01.00 (minor) [skip ci] 2026-05-09 22:14:52 +00:00
jmiller b945deacfb Merge pull request 'chore: cascade main → dev (5a5637f) [skip ci]' (#11) from main into dev 2026-05-09 22:14:38 +00:00
Jonathan Miller 5a5637f521 fix: rename display name to "Moko Web Services - DPCalendar API"
Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Successful in 2s
Repo Health / Scripts governance (push) Successful in 2s
Repo Health / Repository health (push) Failing after 3s
Repository Cleanup / Clean Merged Branches (push) Successful in 4s
All Moko ecosystem extensions must have "Moko" in the display name
for identification on client sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-09 17:14:19 -05:00
jmiller 83bfce3ec4 Merge pull request 'chore: cascade main → dev (2effa61) [skip ci]' (#10) from main into dev 2026-05-09 18:27:00 +00:00
Jonathan Miller 2effa618f1 fix: update repo-health workflow for current standards (dev branch, no docs/)
Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Successful in 2s
Repo Health / Scripts governance (push) Successful in 2s
Repo Health / Repository health (push) Failing after 2s
2026-05-09 13:26:39 -05:00
jmiller 8fc692adc7 Merge pull request 'chore: cascade main → dev (d3dc424) [skip ci]' (#9) from main into dev 2026-05-09 18:20:32 +00:00
Jonathan Miller d3dc424027 release: v03.01.00 — production stable release
Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Failing after 3s
Repo Health / Scripts governance (push) Successful in 2s
Repo Health / Repository health (push) Failing after 3s
Features:
- Events CRUD with filtering, sorting, pagination, search
- Calendars and Locations read API
- Bookings and Tickets read API
- ICS/iCal export (single event + full calendar)
- Recurring event RRULE expansion with occurrence listing
- Bulk event creation endpoint
- CORS with preflight handling
- ETag caching for conditional GET
- Location expansion on events
- Advanced filtering: date ranges, featured, access, language
- Field selection on all endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-09 13:20:17 -05:00
Jonathan Miller 63427aed72 ci: add PHPStan static analysis to CI pipeline [skip ci] 2026-05-07 15:10:39 -05:00
Moko Standards Bot e825441fc0 ci: add Gitleaks secret scanning + Renovate dependency config [skip ci] 2026-05-07 15:00:23 -05:00
Jonathan Miller bbbd287e31 ci: cascade v2 — forward-merge main to all open branches [skip ci] 2026-05-07 14:35:37 -05:00
jmiller ca31a886cf ci: add cascade main → dev workflow [skip ci] 2026-05-05 16:56:03 -05:00
Jonathan Miller 7fe6a0bc0f chore: set platform to joomla
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Failing after 2s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 15:24:36 -05:00
Jonathan Miller 8cbfe0420e fix: WORKFLOWS_DIR .github → .gitea
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Failing after 2s
Repo Health / Scripts governance (push) Successful in 2s
Repo Health / Repository health (push) Failing after 2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 15:00:03 -05:00
Jonathan Miller d572f2860f fix: stable release = minor bump (not major)
Repo Health / Access control (push) Successful in 1s
Repo Health / Release configuration (push) Failing after 2s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 14:38:38 -05:00
gitea-actions[bot] d7ca704e92 chore: update development channel 03.00.01 [skip ci] 2026-05-04 18:59:30 +00:00
gitea-actions[bot] d838a50539 chore(version): bump 03.00.00 → 03.00.01 [skip ci] 2026-05-04 18:59:29 +00:00
Jonathan Miller 6b5025ba4a feat: CORS, caching, tickets endpoint, access/language filters
Repo Health / Access control (push) Successful in 0s
Repo Health / Release configuration (push) Failing after 2s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
- CORS headers on all responses (Access-Control-Allow-Origin: *)
- OPTIONS preflight handling
- ETag + Cache-Control + 304 Not Modified support
- GET /tickets, /tickets/:id with event_id/booking_id filters
- ?access= filter for Joomla access levels
- ?language= filter (includes language=* fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 13:59:16 -05:00
jmiller 2743d23a3f chore: sync updates.xml 03.00.00 [skip ci] 2026-05-04 18:55:25 +00:00
43 changed files with 4567 additions and 274 deletions
-38
View File
@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
MokoStandards Repository Manifest
Auto-generated by MokoStandards bulk sync.
Manual edits to <governance> and <last-synced> may be overwritten.
See: docs/standards/mokostandards-file-spec.md
-->
<mokostandards xmlns="https://standards.mokoconsulting.tech/mokostandards/1.0" schema-version="1.0">
<identity>
<name>MokoDPCalendarAPI</name>
<org>MokoConsulting</org>
<description>Joomla Web Services plugin exposing DPCalendar events, calendars, and locations via REST API</description>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
<platform>waas-component</platform>
<standards-version>04.07.00</standards-version>
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/MokoStandards</standards-source>
<last-synced>2026-05-02T23:06:16+00:00</last-synced>
</governance>
<build>
<language>PHP</language>
<package-type>joomla-extension</package-type>
<entry-point>src/mokodpcalendarapi.xml</entry-point>
</build>
<scripts>
<script name="package" phase="build">
<command>make package</command>
<description>Package via make</description>
<runner>make</runner>
</script>
<script name="clean" phase="build">
<command>make clean</command>
<description>Clean via make</description>
<runner>make</runner>
</script>
</scripts>
</mokostandards>
+110
View File
@@ -0,0 +1,110 @@
---
name: Architecture Decision Record (ADR)
about: Propose or document an architectural decision
title: '[ADR] '
labels: 'architecture, decision'
assignees: ''
---
## ADR Number
ADR-XXXX
## Status
- [ ] Proposed
- [ ] Accepted
- [ ] Deprecated
- [ ] Superseded by ADR-XXXX
## Context
Describe the issue or problem that motivates this decision.
## Decision
State the architecture decision and provide rationale.
## Consequences
### Positive
- List positive consequences
### Negative
- List negative consequences or trade-offs
### Neutral
- List neutral aspects
## Alternatives Considered
### Alternative 1
- Description
- Pros
- Cons
- Why not chosen
### Alternative 2
- Description
- Pros
- Cons
- Why not chosen
## Implementation Plan
1. Step 1
2. Step 2
3. Step 3
## Stakeholders
- **Decision Makers**: @user1, @user2
- **Consulted**: @user3, @user4
- **Informed**: team-name
## Technical Details
### Architecture Diagram
```
[Add diagram or link]
```
### Dependencies
- Dependency 1
- Dependency 2
### Impact Analysis
- **Performance**: [Impact description]
- **Security**: [Impact description]
- **Scalability**: [Impact description]
- **Maintainability**: [Impact description]
## Testing Strategy
- [ ] Unit tests
- [ ] Integration tests
- [ ] Performance tests
- [ ] Security tests
## Documentation
- [ ] Architecture documentation updated
- [ ] API documentation updated
- [ ] Developer guide updated
- [ ] Runbook created
## Migration Path
Describe how to migrate from current state to new architecture.
## Rollback Plan
Describe how to rollback if issues occur.
## Timeline
- **Proposal Date**:
- **Decision Date**:
- **Implementation Start**:
- **Expected Completion**:
## References
- Related ADRs:
- External resources:
- RFCs:
## Review Checklist
- [ ] Aligns with enterprise architecture principles
- [ ] Security implications reviewed
- [ ] Performance implications reviewed
- [ ] Cost implications reviewed
- [ ] Compliance requirements met
- [ ] Team consensus achieved
+48
View File
@@ -0,0 +1,48 @@
---
name: Bug Report
about: Report a bug or issue with the project
title: '[BUG] '
labels: 'bug'
assignees: ''
---
## Bug Description
A clear and concise description of what the bug is.
## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
## Expected Behavior
A clear and concise description of what you expected to happen.
## Actual Behavior
A clear and concise description of what actually happened.
## Screenshots
If applicable, add screenshots to help explain your problem.
## Environment
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
- **Version**: [e.g., 1.2.3]
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
- **PHP Version**: [e.g., 8.1]
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
## Additional Context
Add any other context about the problem here.
## Possible Solution
If you have suggestions on how to fix the issue, please describe them here.
## Checklist
- [ ] I have searched for similar issues before creating this one
- [ ] I have provided all the requested information
- [ ] I have tested this on the latest stable version
- [ ] I have checked the documentation and couldn't find a solution
+18
View File
@@ -0,0 +1,18 @@
---
blank_issues_enabled: true
contact_links:
- name: 💼 Enterprise Support
url: https://mokoconsulting.tech/enterprise
about: Enterprise-level support and consultation services
- name: 💬 Ask a Question
url: https://mokoconsulting.tech/
about: Get help or ask questions through our website
- name: 📚 MokoStandards Documentation
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
about: View our coding standards and best practices
- name: 🔒 Report a Security Vulnerability
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
about: Report security vulnerabilities privately (for critical issues)
- name: 💡 Community Discussions
url: https://github.com/orgs/mokoconsulting-tech/discussions
about: Join community discussions and Q&A
@@ -0,0 +1,52 @@
---
name: Documentation Issue
about: Report an issue with documentation
title: '[DOCS] '
labels: 'documentation'
assignees: ''
---
## Documentation Issue
**Location**:
<!-- Specify the file, page, or section with the issue -->
## Issue Type
<!-- Mark the relevant option with an "x" -->
- [ ] Typo or grammar error
- [ ] Outdated information
- [ ] Missing documentation
- [ ] Unclear explanation
- [ ] Broken links
- [ ] Missing examples
- [ ] Other (specify below)
## Description
<!-- Clearly describe the documentation issue -->
## Current Content
<!-- Quote or describe the current documentation (if applicable) -->
```
Current text here
```
## Suggested Improvement
<!-- Provide your suggestion for how to improve the documentation -->
```
Suggested text here
```
## Additional Context
<!-- Add any other context, screenshots, or references -->
## Standards Alignment
- [ ] Follows MokoStandards documentation guidelines
- [ ] Uses en_US/en_GB localization
- [ ] Includes proper SPDX headers where applicable
## Checklist
- [ ] I have searched for similar documentation issues
- [ ] I have provided a clear description
- [ ] I have suggested an improvement (if applicable)
@@ -0,0 +1,51 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: '[FEATURE] '
labels: 'enhancement'
assignees: ''
---
## Feature Description
A clear and concise description of the feature you'd like to see.
## Problem or Use Case
Describe the problem this feature would solve or the use case it addresses.
Ex. I'm always frustrated when [...]
## Proposed Solution
A clear and concise description of what you want to happen.
## Alternative Solutions
A clear and concise description of any alternative solutions or features you've considered.
## Benefits
Describe how this feature would benefit users:
- Who would use this feature?
- What problems does it solve?
- What value does it add?
## Implementation Details (Optional)
If you have ideas about how this could be implemented, share them here:
- Technical approach
- Files/components that might need changes
- Any concerns or challenges you foresee
## Additional Context
Add any other context, mockups, or screenshots about the feature request here.
## Relevant Standards
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
- [ ] Accessibility (WCAG 2.1 AA)
- [ ] Localization (en_US/en_GB)
- [ ] Security best practices
- [ ] Code quality standards
- [ ] Other: [specify]
## Checklist
- [ ] I have searched for similar feature requests before creating this one
- [ ] I have clearly described the use case and benefits
- [ ] I have considered alternative solutions
- [ ] This feature aligns with the project's goals and scope
+87
View File
@@ -0,0 +1,87 @@
---
name: Joomla Extension Issue
about: Report an issue with a Joomla extension
title: '[JOOMLA] '
labels: 'joomla'
assignees: ''
---
## Issue Type
- [ ] Component Issue
- [ ] Module Issue
- [ ] Plugin Issue
- [ ] Template Issue
## Extension Details
- **Extension Name**: [e.g., moko-cassiopeia]
- **Extension Version**: [e.g., 1.2.3]
- **Extension Type**: [Component / Module / Plugin / Template]
## Joomla Environment
- **Joomla Version**: [e.g., 4.4.0, 5.0.0]
- **PHP Version**: [e.g., 8.1.0]
- **Database**: [MySQL / PostgreSQL / MariaDB]
- **Database Version**: [e.g., 8.0]
- **Server**: [Apache / Nginx / IIS]
- **Hosting**: [Shared / VPS / Dedicated / Cloud]
## Issue Description
Provide a clear and detailed description of the issue.
## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Configure '...'
4. See error
## Expected Behavior
What you expected to happen.
## Actual Behavior
What actually happened.
## Error Messages
```
# Paste any error messages from Joomla error logs
# Location: administrator/logs/error.php
```
## Browser Console Errors
```javascript
// Paste any JavaScript console errors (F12 in browser)
```
## Screenshots
Add screenshots to help explain the issue.
## Configuration
```ini
# Paste extension configuration (sanitize sensitive data)
```
## Installed Extensions
List other installed extensions that might conflict:
- Extension 1 (version)
- Extension 2 (version)
## Template Overrides
- [ ] Using template overrides
- [ ] Custom CSS
- [ ] Custom JavaScript
## Additional Context
- **Multilingual Site**: [Yes / No]
- **Cache Enabled**: [Yes / No]
- **Debug Mode**: [Yes / No]
- **SEF URLs**: [Yes / No]
## Checklist
- [ ] I have cleared Joomla cache
- [ ] I have disabled other extensions to test for conflicts
- [ ] I have checked Joomla error logs
- [ ] I have tested with a default Joomla template
- [ ] I have checked browser console for JavaScript errors
- [ ] I have searched for similar issues
- [ ] I am using a supported Joomla version
+82
View File
@@ -0,0 +1,82 @@
---
name: Question
about: Ask a question about usage, features, or best practices
title: '[QUESTION] '
labels: ['question']
assignees: ['jmiller']
---
## Question
**Your question:**
## Context
**What are you trying to accomplish?**
**What have you already tried?**
**Category**:
- [ ] Script usage
- [ ] Configuration
- [ ] Workflow setup
- [ ] Documentation interpretation
- [ ] Best practices
- [ ] Integration
- [ ] Other: __________
## Environment (if relevant)
**Your setup**:
- Operating System:
- Version:
## What You've Researched
**Documentation reviewed**:
- [ ] README.md
- [ ] Project documentation
- [ ] Other (specify): __________
**Similar issues/questions found**:
- #
- #
## Expected Outcome
**What result are you hoping for?**
## Code/Configuration Samples
**Relevant code or configuration** (if applicable):
```bash
# Your code here
```
## Additional Context
**Any other relevant information:**
**Screenshots** (if helpful):
## Urgency
- [ ] Urgent (blocking work)
- [ ] Normal (can work on other things meanwhile)
- [ ] Low priority (just curious)
## Checklist
- [ ] I have searched existing issues and discussions
- [ ] I have reviewed relevant documentation
- [ ] I have provided sufficient context
- [ ] I have included code/configuration samples if relevant
- [ ] This is a genuine question (not a bug report or feature request)
+126
View File
@@ -0,0 +1,126 @@
---
name: Request for Comments (RFC)
about: Propose a significant change for community discussion
title: '[RFC] '
labels: 'rfc, discussion'
assignees: ''
---
## RFC Summary
One-paragraph summary of the proposal.
## Motivation
Why are we doing this? What use cases does it support? What is the expected outcome?
## Detailed Design
### Overview
Provide a detailed explanation of the proposed change.
### API Changes (if applicable)
```php
// Before
function oldApi($param1) { }
// After
function newApi($param1, $param2) { }
```
### User Experience Changes
Describe how users will interact with this change.
### Implementation Approach
High-level implementation strategy.
## Drawbacks
Why should we *not* do this?
## Alternatives
What other designs have been considered? What is the impact of not doing this?
### Alternative 1
- Description
- Trade-offs
### Alternative 2
- Description
- Trade-offs
## Adoption Strategy
How will existing users adopt this? Is this a breaking change?
### Migration Guide
```bash
# Steps to migrate
```
### Deprecation Timeline
- **Announcement**:
- **Deprecation**:
- **Removal**:
## Unresolved Questions
- Question 1
- Question 2
## Future Possibilities
What future work does this enable?
## Impact Assessment
### Performance
Expected performance impact.
### Security
Security considerations and implications.
### Compatibility
- **Backward Compatible**: [Yes / No]
- **Breaking Changes**: [List]
### Maintenance
Long-term maintenance considerations.
## Community Input
### Stakeholders
- [ ] Core team
- [ ] Module developers
- [ ] End users
- [ ] Enterprise customers
### Feedback Period
**Duration**: [e.g., 2 weeks]
**Deadline**: [date]
## Implementation Timeline
### Phase 1: Design
- [ ] RFC discussion
- [ ] Design finalization
- [ ] Approval
### Phase 2: Implementation
- [ ] Core implementation
- [ ] Tests
- [ ] Documentation
### Phase 3: Release
- [ ] Beta release
- [ ] Feedback collection
- [ ] Stable release
## Success Metrics
How will we measure success?
- Metric 1
- Metric 2
## References
- Related RFCs:
- External documentation:
- Prior art:
## Open Questions for Community
1. Question 1?
2. Question 2?
---
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
+51
View File
@@ -0,0 +1,51 @@
---
name: Security Vulnerability Report
about: Report a security vulnerability (use only for non-critical issues)
title: '[SECURITY] '
labels: 'security'
assignees: ''
---
## ⚠️ IMPORTANT: Private Disclosure Required
**For critical security vulnerabilities, DO NOT use this template.**
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
Use this template only for:
- Security improvements
- Non-critical security suggestions
- Security documentation updates
---
## Security Issue
**Severity**:
<!-- Low, Medium, or informational only -->
## Description
<!-- Describe the security concern or improvement suggestion -->
## Affected Components
<!-- List the affected files, features, or components -->
## Suggested Mitigation
<!-- Describe how this could be addressed -->
## Standards Reference
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
- [ ] SPDX license identifiers
- [ ] Secret management
- [ ] Dependency security
- [ ] Access control
- [ ] Other: [specify]
## Additional Context
<!-- Add any other context about the security concern -->
## Checklist
- [ ] This is NOT a critical vulnerability requiring private disclosure
- [ ] I have reviewed the SECURITY.md policy
- [ ] I have provided sufficient detail for evaluation
+24
View File
@@ -0,0 +1,24 @@
---
name: Version Bump
about: Request or track a version change
title: '[VERSION] '
labels: 'version, type: version'
assignees: 'jmiller'
---
## Version Change
**Current version**: <!-- e.g., 01.02.03 -->
**Requested version**: <!-- e.g., 01.03.00 -->
**Change type**: <!-- patch / minor / major -->
## Reason
<!-- Why is this version bump needed? -->
## Checklist
- [ ] README.md `VERSION:` field updated
- [ ] CHANGELOG.md entry added
- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: `<version>`)
- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow
@@ -117,8 +117,8 @@ jobs:
echo "Version: $VERSION (patch — platform version + badges only)" echo "Version: $VERSION (patch — platform version + badges only)"
fi fi
# -- STEP 1b: Bump major version (stable = major bump, reset minor+patch) - # -- STEP 1b: Bump minor version (stable = minor bump, reset patch) ------
- name: "Step 1b: Bump major version for stable release" - name: "Step 1b: Bump minor version for stable release"
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
id: bump id: bump
run: | run: |
@@ -126,14 +126,19 @@ jobs:
[ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } [ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1))) MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1)))
MINOR=$((10#$(echo "$CURRENT" | cut -d. -f2)))
# Major bump, reset minor and patch # Minor bump, reset patch. Rollover if minor > 99
MAJOR=$((MAJOR + 1)) MINOR=$((MINOR + 1))
if [ $MINOR -gt 99 ]; then
MINOR=0
MAJOR=$((MAJOR + 1))
fi
VERSION=$(printf "%02d.00.00" $MAJOR) VERSION=$(printf "%02d.%02d.00" $MAJOR $MINOR)
TODAY=$(date +%Y-%m-%d) TODAY=$(date +%Y-%m-%d)
echo "Stable bump: ${CURRENT} → ${VERSION} (major)" echo "Stable bump: ${CURRENT} → ${VERSION} (minor)"
# Update README.md # Update README.md
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
@@ -146,13 +151,22 @@ jobs:
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST" sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST"
fi fi
# Promote [Unreleased] section in CHANGELOG.md to new version
if [ -f "CHANGELOG.md" ] && grep -qi "Unreleased" CHANGELOG.md; then
sed -i "s|## \[Unreleased\]|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
sed -i "s|## Unreleased|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
sed -i "2i ## [Unreleased]" CHANGELOG.md
sed -i "3i \\ " CHANGELOG.md
echo "CHANGELOG promoted to [${VERSION}]"
fi
# Commit and push # Commit and push
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "gitea-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 || {
git commit -m "chore(version): bump ${CURRENT} → ${VERSION} (major) [skip ci]" git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]"
git push origin HEAD:main 2>&1 git push origin HEAD:main 2>&1
} }
@@ -306,6 +320,7 @@ jobs:
# -- STEP 5: Write updates.xml (Joomla update server) --------------------- # -- STEP 5: Write updates.xml (Joomla update server) ---------------------
- name: "Step 5: Write updates.xml" - name: "Step 5: Write updates.xml"
id: updates
if: >- if: >-
steps.version.outputs.skip != 'true' && steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true' steps.check.outputs.already_released != 'true'
@@ -329,20 +344,44 @@ jobs:
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1) TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
# If EXT_NAME is a language key (e.g. PLG_SYSTEM_MOKOJGDPC), resolve from .ini
if echo "$EXT_NAME" | grep -qE '^[A-Z_]+$'; then
INI_NAME=$(find . -name "*.sys.ini" -path "*/en-GB/*" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
[ -z "$INI_NAME" ] && INI_NAME=$(find . -name "*.sys.ini" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
[ -n "$INI_NAME" ] && EXT_NAME="$INI_NAME"
fi
# Fallbacks # Fallbacks
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component" [ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Derive element if not in manifest: # Derive element if not in manifest:
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas) # 1. plugin="xxx" attribute (plugins)
# 2. Fall back to repo name (lowercased) # 2. module="xxx" attribute (modules)
# 3. XML filename (components, packages)
# 4. Repo name fallback (templates, anything else)
if [ -z "$EXT_ELEMENT" ]; then if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
fi
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(sed -n 's/.*module="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
fi
if [ -z "$EXT_ELEMENT" ]; then
FNAME=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
# If filename is generic (templateDetails, manifest), use repo name # If filename is generic (templateDetails, manifest), use repo name
case "$EXT_ELEMENT" in case "$FNAME" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
*) EXT_ELEMENT="$FNAME" ;;
esac esac
fi fi
# Final fallback
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
# Save for Steps 7, 8, 8b
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "ext_name=${EXT_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_type=${EXT_TYPE}" >> "$GITHUB_OUTPUT"
echo "ext_folder=${EXT_FOLDER}" >> "$GITHUB_OUTPUT"
# Build client tag: plugins and frontend modules need <client>site</client> # Build client tag: plugins and frontend modules need <client>site</client>
CLIENT_TAG="" CLIENT_TAG=""
@@ -369,7 +408,18 @@ jobs:
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>" PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi fi
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip" # Build TYPE_PREFIX for download URL
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable" INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
# -- Build update entry for a given stability tag # -- Build update entry for a given stability tag
@@ -464,21 +514,32 @@ jobs:
MAJOR="${{ steps.version.outputs.major }}" MAJOR="${{ steps.version.outputs.major }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Auto-detect extension element for release naming # Reuse metadata from Step 5 (single source of truth)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
EXT_ELEMENT="" EXT_NAME="${{ steps.updates.outputs.ext_name }}"
if [ -n "$MANIFEST" ]; then EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1) EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; esac # Fallbacks if Step 5 was skipped
else if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi fi
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}" [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
RELEASE_NAME="${EXT_ELEMENT} ${VERSION} (stable)" # Build release name: "Pretty Name VERSION (type_element-VERSION)"
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})"
# Delete existing release if present (overwrite, not append) # Delete existing release if present (overwrite, not append)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
@@ -528,9 +589,28 @@ jobs:
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true) MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0 [ -z "$MANIFEST" ] && exit 0
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) # Reuse element from Step 5, with same fallback chain
ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz" if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
# -- Build install packages from src/ ---------------------------- # -- Build install packages from src/ ----------------------------
SOURCE_DIR="src" SOURCE_DIR="src"
@@ -670,6 +750,73 @@ jobs:
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
# -- STEP 8b: Update release description with changelog + SHA ----------------
- name: "Step 8b: Update release body with changelog and SHA"
if: steps.version.outputs.skip != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
# Build TYPE_PREFIX to match Step 8's ZIP naming
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
# Get SHA from the built files
SHA256_ZIP=""
[ -f "/tmp/${ZIP_NAME}" ] && SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
SHA256_TAR=""
[ -f "/tmp/${TAR_NAME}" ] && SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
# Extract latest changelog entry (strip the ## header to avoid duplicate)
CHANGELOG=""
if [ -f "CHANGELOG.md" ]; then
CHANGELOG=$(sed -n "/^## \[*${VERSION}/,/^## \[*[0-9]/p" CHANGELOG.md | sed '$d' | sed '1d')
[ -z "$CHANGELOG" ] && CHANGELOG=$(sed -n '/^## /,/^## /p' CHANGELOG.md | sed '$d' | sed '1d' | head -30)
fi
# Build release body (single header, no duplicate from changelog)
BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\n"
if [ -n "$CHANGELOG" ]; then
BODY="${BODY}${CHANGELOG}\n\n"
fi
BODY="${BODY}---\n\n### Checksums\n\n"
BODY="${BODY}| File | SHA-256 |\n|------|--------|\n"
[ -n "$SHA256_ZIP" ] && BODY="${BODY}| \`${ZIP_NAME}\` | \`${SHA256_ZIP}\` |\n"
[ -n "$SHA256_TAR" ] && BODY="${BODY}| \`${TAR_NAME}\` | \`${SHA256_TAR}\` |\n"
# Get release ID and update body
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
python3 -c "
import json, urllib.request
body = '''$(printf '%b' "$BODY")'''
data = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=data,
headers={'Authorization': 'token ${{ secrets.GA_TOKEN }}', 'Content-Type': 'application/json'},
method='PATCH'
)
urllib.request.urlopen(req)
" 2>/dev/null && echo "Release body updated with changelog + SHA" >> $GITHUB_STEP_SUMMARY
fi
# -- STEP 9: Mirror to GitHub (stable only) -------------------------------- # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub" - name: "Step 9: Mirror release to GitHub"
if: >- if: >-
@@ -759,6 +906,26 @@ jobs:
done done
echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY
# -- STEP 11: Reset dev branch from main ------------------------------------
- name: "Step 11: Delete and recreate dev branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.GA_TOKEN }}"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# 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 "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
# -- Summary -------------------------------------------------------------- # -- Summary --------------------------------------------------------------
- name: Pipeline Summary - name: Pipeline Summary
if: always() if: always()
+213
View File
@@ -0,0 +1,213 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/cascade-dev.yml.template
# VERSION: 02.00.00
# BRIEF: Forward-merge main → all open branches after every push to main
#
# +========================================================================+
# | CASCADE MAIN → ALL BRANCHES |
# +========================================================================+
# | |
# | Triggers on every push to main (PR merges, bot commits, etc.) |
# | |
# | 1. List all branches matching: dev, rc/*, beta/*, alpha/* |
# | 2. For each: create PR (main → branch), auto-merge if clean |
# | 3. On conflict: leave PR open for manual resolution |
# | |
# +========================================================================+
name: Cascade Main → Dev
on:
push:
branches:
- main
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
pull-requests: write
jobs:
cascade:
name: Cascade main → branches
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip cascade]')
steps:
- name: Discover target branches
id: branches
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Fetch all branches (paginated)
PAGE=1
ALL_BRANCHES=""
while true; do
BATCH=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/branches?page=${PAGE}&limit=50" \
| jq -r '.[].name // empty')
[ -z "$BATCH" ] && break
ALL_BRANCHES="$ALL_BRANCHES $BATCH"
PAGE=$((PAGE + 1))
done
# Filter to cascade targets: dev, dev/*, rc/*, beta/*, alpha/*
TARGETS=""
for BRANCH in $ALL_BRANCHES; do
case "$BRANCH" in
dev|dev/*|rc/*|beta/*|alpha/*)
TARGETS="$TARGETS $BRANCH"
;;
esac
done
TARGETS=$(echo "$TARGETS" | xargs) # trim whitespace
if [ -z "$TARGETS" ]; then
echo "targets=" >> "$GITHUB_OUTPUT"
echo "️ No cascade target branches found"
else
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
COUNT=$(echo "$TARGETS" | wc -w)
echo "📋 Found ${COUNT} target branch(es): ${TARGETS}"
fi
- name: Cascade to all target branches
if: steps.branches.outputs.targets != ''
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
SHORT_SHA="${GITHUB_SHA:0:7}"
TARGETS="${{ steps.branches.outputs.targets }}"
SUCCESS=0
CONFLICTS=0
SKIPPED=0
FAILED=0
for BRANCH in $TARGETS; do
echo ""
echo "═══ main → ${BRANCH} ═══"
# Check if branch is already up to date
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
RESPONSE=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/compare/${ENCODED_BRANCH}...main")
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
if [ "$AHEAD" -eq 0 ]; then
echo " ✅ Already up to date"
SKIPPED=$((SKIPPED + 1))
continue
fi
echo " ️ main is ${AHEAD} commit(s) ahead"
# Check for existing cascade PR
EXISTING=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
PR_NUMBER=""
if [ "$EXISTING_COUNT" -gt 0 ]; then
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
echo " ️ Reusing existing PR #${PR_NUMBER}"
else
# Create cascade PR
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
\"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\",
\"head\": \"main\",
\"base\": \"${BRANCH}\"
}" \
"${API}/pulls")
HTTP_CODE=$(echo "$PR_RESPONSE" | tail -1)
BODY=$(echo "$PR_RESPONSE" | sed '$d')
PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty')
if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then
MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)
echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
FAILED=$((FAILED + 1))
continue
fi
echo " ✅ Created PR #${PR_NUMBER}"
fi
# Try auto-merge
PR_DATA=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls/${PR_NUMBER}")
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
if [ "$MERGEABLE" != "true" ]; then
echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
continue
fi
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"Do\": \"merge\",
\"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\",
\"delete_branch_after_merge\": false
}" \
"${API}/pulls/${PR_NUMBER}/merge")
MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1)
if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then
echo " ✅ Merged — ${BRANCH} is in sync"
SUCCESS=$((SUCCESS + 1))
else
MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d')
echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
fi
done
# Summary
echo ""
echo "════════════════════════════════════════"
echo " ✅ Merged: ${SUCCESS}"
echo " ⚠️ Conflicts: ${CONFLICTS}"
echo " ⏭️ Up to date: ${SKIPPED}"
echo " ❌ Failed: ${FAILED}"
echo "════════════════════════════════════════"
if [ "$FAILED" -gt 0 ]; then
exit 1
fi
@@ -375,3 +375,76 @@ jobs:
else else
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
fi 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ 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
+96
View File
@@ -0,0 +1,96 @@
# 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-tech/MokoStandards-API
# 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: Secret Scanning
on:
pull_request:
branches:
- main
- 'dev/**'
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 || 'gitea-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
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity>
<name>MokoDPCalendarAPI</name>
<org>MokoConsulting</org>
<description>Joomla Web Services plugin exposing DPCalendar events, calendars, and locations via REST API</description>
<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>
</governance>
<build>
<language>PHP</language>
<package-type>joomla</package-type>
<entry-point>src/</entry-point>
</build>
</moko-platform>
@@ -18,6 +18,7 @@ on:
- "Joomla Build & Release" - "Joomla Build & Release"
- "Joomla Extension CI" - "Joomla Extension CI"
- "Deploy" - "Deploy"
- "Cascade Main → Dev"
types: types:
- completed - completed
+90
View File
@@ -0,0 +1,90 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Enforces branch merge policy:
# feature/* → dev only
# fix/* → dev only
# hotfix/* → dev or main (emergency)
# dev → main only
# alpha/* → dev only
# beta/* → dev only
# rc/* → main only
name: Branch Policy Check
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
check-target:
name: Verify merge target
runs-on: ubuntu-latest
steps:
- name: Check branch policy
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
;;
hotfix/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
fi
;;
alpha/*|beta/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Pre-release branches must target 'dev', not '${BASE}'"
fi
;;
rc/*)
if [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Release candidate branches must target '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 ""
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
@@ -6,11 +6,11 @@
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.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: /templates/workflows/universal/pre-release.yml.template
# VERSION: 01.00.00 # VERSION: 05.00.00
# BRIEF: Manual pre-release — builds dev/alpha/beta/rc packages from any branch # BRIEF: Manual pre-release — builds dev/alpha/beta/rc packages from any branch
name: Pre-Release name: "Universal: Pre-Release"
on: on:
workflow_dispatch: workflow_dispatch:
@@ -52,6 +52,20 @@ jobs:
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip >/dev/null 2>&1 sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip >/dev/null 2>&1
fi fi
- name: Detect platform
id: platform
run: |
PLATFORM=$(cat .mokogitea/.moko-platform 2>/dev/null | tr -d '[:space:]')
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
# For packages: prefer pkg_*.xml in src/; fallback to any manifest
MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
- name: Resolve metadata - name: Resolve metadata
id: meta id: meta
run: | run: |
@@ -94,13 +108,36 @@ jobs:
# Update README.md # Update README.md
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
# Update manifest # Update platform-specific manifest
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$MANIFEST" ]; then MANIFEST="${{ steps.platform.outputs.manifest }}"
MANIFEST_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) MOD_FILE="${{ steps.platform.outputs.mod_file }}"
sed -i "s|<version>${MANIFEST_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST" case "$PLATFORM" in
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST" joomla)
fi if [ -n "$MANIFEST" ]; then
MANIFEST_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
sed -i "s|<version>${MANIFEST_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST"
fi
# For packages: also bump version in all sub-extension manifests
if [ -d "src/packages" ]; then
for SUB_MANIFEST in $(find src/packages -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null); do
SUB_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$SUB_MANIFEST" | head -1)
if [ -n "$SUB_VER" ]; then
sed -i "s|<version>${SUB_VER}</version>|<version>${VERSION}</version>|" "$SUB_MANIFEST"
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$SUB_MANIFEST"
echo " Bumped sub-extension: $(basename $SUB_MANIFEST) ${SUB_VER} → ${VERSION}"
fi
done
fi
;;
dolibarr)
if [ -n "$MOD_FILE" ]; then
sed -i "s/\$this->version = '[^']*'/\$this->version = '${VERSION}'/" "$MOD_FILE"
fi
;;
*) ;;
esac
# Commit version bump # Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
@@ -112,20 +149,36 @@ jobs:
git push origin HEAD 2>&1 git push origin HEAD 2>&1
} }
# Auto-detect element from manifest # Auto-detect element (platform-aware)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) case "$PLATFORM" in
EXT_ELEMENT="" joomla)
if [ -n "$MANIFEST" ]; then MANIFEST="${{ steps.platform.outputs.manifest }}"
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1) EXT_ELEMENT=""
if [ -z "$EXT_ELEMENT" ]; then if [ -n "$MANIFEST" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
case "$EXT_ELEMENT" in if [ -z "$EXT_ELEMENT" ]; then
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
esac case "$EXT_ELEMENT" in
fi templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
else esac
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') fi
fi else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
dolibarr)
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
if [ -n "$MOD_FILE" ]; then
MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
*)
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
;;
esac
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip" ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
@@ -148,17 +201,49 @@ jobs:
exit 1 exit 1
fi fi
MANIFEST="${{ steps.meta.outputs.manifest }}"
EXT_TYPE=""
if [ -n "$MANIFEST" ]; then
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
fi
EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
mkdir -p build/package mkdir -p build/package
rsync -a \
--exclude='sftp-config*' \ if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
--exclude='.ftpignore' \ echo "=== Building Joomla PACKAGE (multi-extension) ==="
--exclude='*.ppk' \
--exclude='*.pem' \ # 1) ZIP each sub-extension in src/packages/
--exclude='*.key' \ for ext_dir in "${SOURCE_DIR}"/packages/*/; do
--exclude='.env*' \ [ ! -d "$ext_dir" ] && continue
--exclude='*.local' \ EXT_NAME=$(basename "$ext_dir")
--exclude='.build-trigger' \ echo " Packaging sub-extension: ${EXT_NAME}"
"${SOURCE_DIR}/" build/package/ cd "$ext_dir"
zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
cd "$OLDPWD"
done
# 2) Copy package-level files (manifest, script, etc.)
for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
[ -f "$f" ] && cp "$f" build/package/
done
echo "Package contents:"
ls -la build/package/
else
echo "=== Building standard Joomla extension ==="
rsync -a \
--exclude='sftp-config*' \
--exclude='.ftpignore' \
--exclude='*.ppk' \
--exclude='*.pem' \
--exclude='*.key' \
--exclude='.env*' \
--exclude='*.local' \
--exclude='.build-trigger' \
"${SOURCE_DIR}/" build/package/
fi
- name: Create ZIP - name: Create ZIP
id: zip id: zip
@@ -222,6 +307,7 @@ jobs:
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})" echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
- name: Update updates.xml - name: Update updates.xml
if: steps.platform.outputs.platform == 'joomla'
run: | run: |
STABILITY="${{ steps.meta.outputs.stability }}" STABILITY="${{ steps.meta.outputs.stability }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
@@ -278,7 +364,7 @@ jobs:
f.write(content) f.write(content)
PYEOF PYEOF
# Commit and push # 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 "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "gitea-actions[bot]"
@@ -287,6 +373,29 @@ jobs:
git push origin HEAD 2>&1 || echo "WARNING: push failed" git push origin HEAD 2>&1 || echo "WARNING: push failed"
fi fi
- name: "Sync updates.xml to all branches"
if: steps.platform.outputs.platform == 'joomla'
run: |
CURRENT_BRANCH="${{ github.ref_name }}"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
# Sync updates.xml to main and dev (whichever isn't current)
for BRANCH in main dev; do
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
echo "Syncing updates.xml → ${BRANCH}"
git fetch origin "${BRANCH}" 2>/dev/null || continue
git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
git checkout "${CURRENT_BRANCH}" -- updates.xml
if ! git diff --quiet updates.xml 2>/dev/null; then
git add updates.xml
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
fi
git checkout "${CURRENT_BRANCH}" 2>/dev/null
done
- name: "Delete lesser pre-release channels (cascade)" - name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true continue-on-error: true
run: | run: |
@@ -64,7 +64,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: .github/workflows WORKFLOWS_DIR: .gitea/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
+193
View File
@@ -0,0 +1,193 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Workflows
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/sync-roadmap-wiki.yml.template
# VERSION: 04.06.00
# BRIEF: Syncs project board state to a Roadmap wiki page
name: Sync Roadmap to Wiki
on:
# Run when project issues change
issues:
types: [opened, closed, reopened, labeled, unlabeled, milestoned, demilestoned]
# Run on milestone changes
milestone:
types: [created, closed, opened, edited, deleted]
# Manual trigger
workflow_dispatch:
# Weekly refresh to catch any drift
schedule:
- cron: '0 6 * * 1'
permissions:
contents: write
issues: read
jobs:
sync-roadmap:
name: Generate Roadmap Wiki
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate Roadmap from Projects
env:
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
GITEA_URL: ${{ github.server_url }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
set -euo pipefail
API="${GITEA_URL}/api/v1"
AUTH="Authorization: token ${GITEA_TOKEN}"
REPO="${REPO_OWNER}/${REPO_NAME}"
# Fetch milestones (open + closed)
MILESTONES_OPEN=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/milestones?state=open&limit=50" || echo "[]")
MILESTONES_CLOSED=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/milestones?state=closed&limit=50" || echo "[]")
# Fetch all open issues
ISSUES_OPEN=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/issues?state=open&type=issues&limit=50" || echo "[]")
ISSUES_CLOSED=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/issues?state=closed&type=issues&limit=50&sort=updated&direction=desc" || echo "[]")
# Fetch labels for categorization
LABELS=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/labels?limit=50" || echo "[]")
# Build the roadmap markdown
cat > /tmp/roadmap.md << 'HEADER'
# Roadmap
> Auto-generated from project milestones and issues.
> Last updated: TIMESTAMP
HEADER
sed -i "s|TIMESTAMP|$(date -u '+%Y-%m-%d %H:%M UTC')|" /tmp/roadmap.md
# --- Active Milestones ---
echo "## Active Milestones" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
MILESTONE_COUNT=$(echo "$MILESTONES_OPEN" | jq 'length')
if [ "$MILESTONE_COUNT" -eq 0 ]; then
echo "_No active milestones._" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
else
echo "$MILESTONES_OPEN" | jq -r '.[] | @base64' | while read -r ms; do
_jq() { echo "$ms" | base64 -d | jq -r "$1"; }
TITLE=$(_jq '.title')
DESC=$(_jq '.description // ""')
DUE=$(_jq '.due_on // ""')
OPEN=$(_jq '.open_issues')
CLOSED=$(_jq '.closed_issues')
TOTAL=$((OPEN + CLOSED))
if [ "$TOTAL" -gt 0 ]; then
PCT=$((CLOSED * 100 / TOTAL))
else
PCT=0
fi
echo "### ${TITLE}" >> /tmp/roadmap.md
if [ -n "$DUE" ] && [ "$DUE" != "null" ] && [ "$DUE" != "0001-01-01T00:00:00Z" ]; then
DUE_FMT=$(date -d "$DUE" '+%B %d, %Y' 2>/dev/null || echo "$DUE")
echo "**Due:** ${DUE_FMT}" >> /tmp/roadmap.md
fi
if [ -n "$DESC" ] && [ "$DESC" != "null" ]; then
echo "" >> /tmp/roadmap.md
echo "$DESC" >> /tmp/roadmap.md
fi
echo "" >> /tmp/roadmap.md
echo "**Progress:** ${CLOSED}/${TOTAL} (${PCT}%)" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
# List issues in this milestone
MS_ID=$(_jq '.id')
MS_ISSUES=$(echo "$ISSUES_OPEN" | jq --arg id "$MS_ID" '[.[] | select(.milestone.id == ($id | tonumber))]')
MS_DONE=$(echo "$ISSUES_CLOSED" | jq --arg id "$MS_ID" '[.[] | select(.milestone.id == ($id | tonumber))]')
if [ "$(echo "$MS_DONE" | jq 'length')" -gt 0 ]; then
echo "$MS_DONE" | jq -r '.[] | "- [x] " + .title + " (#" + (.number | tostring) + ")"' >> /tmp/roadmap.md
fi
if [ "$(echo "$MS_ISSUES" | jq 'length')" -gt 0 ]; then
echo "$MS_ISSUES" | jq -r '.[] | "- [ ] " + .title + " (#" + (.number | tostring) + ")"' >> /tmp/roadmap.md
fi
echo "" >> /tmp/roadmap.md
done
fi
# --- Backlog (issues without milestones) ---
BACKLOG=$(echo "$ISSUES_OPEN" | jq '[.[] | select(.milestone == null)]')
BACKLOG_COUNT=$(echo "$BACKLOG" | jq 'length')
if [ "$BACKLOG_COUNT" -gt 0 ]; then
echo "## Backlog" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
echo "_Issues not yet assigned to a milestone._" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
# Group by label if possible
echo "$BACKLOG" | jq -r '.[] | "- [ ] " + .title + " (#" + (.number | tostring) + ")" + (if (.labels | length) > 0 then " `" + (.labels | map(.name) | join("`, `")) + "`" else "" end)' >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
fi
# --- Completed Milestones ---
CLOSED_COUNT=$(echo "$MILESTONES_CLOSED" | jq 'length')
if [ "$CLOSED_COUNT" -gt 0 ]; then
echo "## Completed" >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
echo "$MILESTONES_CLOSED" | jq -r '.[] | "- ~~" + .title + "~~ ✓ (" + (.closed_issues | tostring) + " issues)"' >> /tmp/roadmap.md
echo "" >> /tmp/roadmap.md
fi
echo "---" >> /tmp/roadmap.md
echo "_Generated by [sync-roadmap-wiki](${GITEA_URL}/${REPO}/actions) workflow._" >> /tmp/roadmap.md
echo "=== Generated Roadmap ==="
cat /tmp/roadmap.md
- name: Push Roadmap to Wiki
env:
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
GITEA_URL: ${{ github.server_url }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
API="${GITEA_URL}/api/v1"
AUTH="Authorization: token ${GITEA_TOKEN}"
REPO="${REPO_OWNER}/${REPO_NAME}"
CONTENT_B64=$(base64 -w0 /tmp/roadmap.md)
# Check if Roadmap wiki page exists
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -H "$AUTH" "${API}/repos/${REPO}/wiki/page/Roadmap" || echo "404")
if [ "$STATUS" = "200" ]; then
# Update existing page
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
"${API}/repos/${REPO}/wiki/page/Roadmap" \
-d "{\"title\": \"Roadmap\", \"content_base64\": \"${CONTENT_B64}\", \"message\": \"chore: sync roadmap from project board\"}" \
&& echo "Roadmap wiki page updated"
else
# Create new page
curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
"${API}/repos/${REPO}/wiki/new" \
-d "{\"title\": \"Roadmap\", \"content_base64\": \"${CONTENT_B64}\", \"message\": \"chore: create roadmap from project board\"}" \
&& echo "Roadmap wiki page created"
fi
- name: Summary
if: always()
run: |
echo "## Roadmap Sync" >> $GITHUB_STEP_SUMMARY
echo "Roadmap wiki page synced from milestones and issues." >> $GITHUB_STEP_SUMMARY
echo "View it at: ${{ github.server_url }}/${{ github.repository }}/wiki/Roadmap" >> $GITHUB_STEP_SUMMARY
+283
View File
@@ -0,0 +1,283 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, updates.xml, 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, closed]
branches:
- main
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
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_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
if: >-
(github.event.action == 'opened' && 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.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
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
# Always fetch latest CLI tools — never use stale cache from previous runs
rm -rf /tmp/moko-platform-api
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
- name: Rename branch to rc
run: |
php /tmp/moko-platform-api/cli/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_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 "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
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.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_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 moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
# Ensure PHP + Composer are available
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
# Always fetch latest CLI tools — never use stale cache from previous runs
rm -rf /tmp/moko-platform-api
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
- name: "Publish stable release"
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
# -- 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="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_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 and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_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"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# 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"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_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="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_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](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+467
View File
@@ -0,0 +1,467 @@
# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Clone MokoStandards
env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_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, namespace (Joomla 5+)
for TAG in name version author namespace; 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
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
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP ${{ matrix.php }}
run: |
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_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.MOKOGITEA_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 ${GITEA_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
+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: Gitea.Workflow
# INGROUP: moko-platform.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/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:
GITEA_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.MOKOGITEA_TOKEN }}
- name: Delete merged branches
env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
for BRANCH in $BRANCHES; do
# Skip protected branches
case "$BRANCH" in
main|master|develop|release/*|hotfix/*) 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 ${GITEA_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:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${GITEA_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 ${GITEA_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 ${GITEA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
echo "Deleted ${DELETED} old workflow run(s)"
+126
View File
@@ -0,0 +1,126 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+96
View File
@@ -0,0 +1,96 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# 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:
pull_request:
branches:
- main
- 'dev/**'
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 || 'gitea-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
+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: Gitea.Workflow
# INGROUP: moko-platform.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/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:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
types:
- completed
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-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}"
+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: Gitea.Workflow
# INGROUP: moko-platform.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# 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
# ── Code Validation ────────────────────────────────────────────────────
validate:
name: Validate PR
runs-on: ubuntu-latest
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: |
# Read platform from XML manifest (<platform> tag) or plain text fallback
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
- 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: 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
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: 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]
steps:
- name: Trigger RC pre-release
env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
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\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ──────────────────────────────────────────────────────
report-issues:
name: Report Issues
runs-on: ubuntu-latest
needs: [branch-policy, validate]
if: >-
always() &&
needs.validate.result == 'failure'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: automation/ci-issue-reporter.sh
sparse-checkout-cone-mode: false
- 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."
+711
View File
@@ -0,0 +1,711 @@
# ============================================================================
# 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: Gitea.Workflow
# INGROUP: moko-platform.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# 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:
push:
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,.mokogitea/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: .mokogitea/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.MOKOGITEA_TOKEN || secrets.MOKOGITEA_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|gitea-actions[bot])
ALLOWED=true
PERMISSION=admin
METHOD="hardcoded allowlist"
;;
*)
# Detect platform and check permissions via API
API_BASE="${GITHUB_API_URL:-${GITEA_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 MokoGitea |'
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-issues:
name: "Report Issues"
runs-on: ubuntu-latest
needs: [access_check, scripts_governance, repo_health]
if: >-
always() &&
(needs.scripts_governance.result == 'failure' ||
needs.repo_health.result == 'failure')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: automation/ci-issue-reporter.sh
sparse-checkout-cone-mode: false
- name: "File issues for failed gates"
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x automation/ci-issue-reporter.sh
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."
+98
View File
@@ -0,0 +1,98 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# 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
- name: Joomla version audit
if: always()
run: |
if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then
echo "$JOOMLA_SITES" > /tmp/sites.json
php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true
echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY
rm -f /tmp/sites.json
else
echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)"
fi
env:
JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }}
+6 -19
View File
@@ -1,24 +1,11 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
VERSION: 01.00.00
PATH: ./CHANGELOG.md
-->
# CHANGELOG - MokoDPCalendarAPI
All notable changes to this project will be documented in this file. ## [Unreleased]
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
## [01.00.00] — 2026-04-26 - Template source paths updated: `templates/gitea/` to `templates/mokogitea/`
- HCL definition files removed -- Template repos are now the canonical source
### Added ### Added
- Initial release of the DPCalendar Web Services plugin - `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
- Events API: list, get, create, update, delete via `/v1/dpcalendar/events`
- Calendars API: list, get via `/v1/dpcalendar/calendars`
- Locations API: list, get, create, update, delete via `/v1/dpcalendar/locations`
- JSON:API views with configurable field lists for items and collections
- Joomla 5/6 namespace-based plugin architecture
- MokoStandards-compliant file headers and structure
+42
View File
@@ -0,0 +1,42 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with this repository.
## Project Overview
**MokoDPCalendarAPI** -- Joomla Web Services plugin exposing DPCalendar events, calendars, and locations via REST API
| Field | Value |
|---|---|
| **Platform** | joomla |
| **Language** | PHP |
| **Default branch** | main |
| **License** | GPL-3.0-or-later |
| **Wiki** | [MokoDPCalendarAPI Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki) |
| **Standards** | [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home) |
## Common Commands
```bash
composer install # Install PHP dependencies
```
## Architecture
This is a Joomla extension. Key directories:
- `src/` -- extension source (deployed to Joomla)
- `src/*.xml` -- manifest file (version, files, params)
- `src/src/` or `src/services/` -- PHP classes
- `src/language/` -- translation strings
- `src/media/` -- CSS/JS/images
## Rules
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, or `*.min.css`/`*.min.js`
- **Attribution**: use `Authored-by: Moko Consulting` in commits
- **Branch strategy**: develop on `dev`, merge to `main` for release
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
+246 -71
View File
@@ -1,94 +1,269 @@
<!-- 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: MokoDPCalendarAPI.Documentation
INGROUP: MokoDPCalendarAPI
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
VERSION: 03.00.00
PATH: ./README.md
BRIEF: Joomla Web Services plugin for DPCalendar
-->
[![Version](https://img.shields.io/badge/version-01.00.00-blue.svg?logo=v&logoColor=white)](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE)
[![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net)
# MokoDPCalendarAPI # MokoDPCalendarAPI
A Joomla 5/6 Web Services plugin that exposes DPCalendar events, calendars, and locations through the Joomla REST API (`/api/index.php/v1`). Joomla Web Services plugin exposing **18 REST endpoints** for DPCalendar events, calendars, locations, bookings, and tickets.
Enables AI assistants (via joomla-api-mcp) and external integrations to create, read, update, and delete DPCalendar content programmatically. ![PHP 8.1+](https://img.shields.io/badge/PHP-8.1%2B-777BB4?style=flat-square&logo=php&logoColor=white) ![Joomla 5.x/6.x](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-5091CD?style=flat-square&logo=joomla&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square) ![Version](https://img.shields.io/badge/version-03.01.00-blue?style=flat-square) ![Wiki](https://img.shields.io/badge/wiki-MokoDPCalendarAPI-blue?style=flat-square)
## Table of Contents ---
- [Background](#background) ## Features
- [Install](#install)
- [API Endpoints](#api-endpoints)
- [Contributing](#contributing)
- [License](#license)
## Background - **18 REST endpoints** across 5 resources (events, calendars, locations, bookings, tickets)
- **CRUD operations** for events including bulk creation
- **iCal export** for events and calendars (`.ics` format)
- **Recurring event expansion** -- RRULE processing with EXDATE support
- **Pagination** with `limit`/`offset` (max 100 per page)
- **Sorting** by 6 fields with ascending/descending order
- **Filtering** by date range, category, search term, featured, access level, language
- **Field selection** -- request only the fields you need
- **Location expansion** -- inline location data with events via `expand=locations`
- **ETag caching** with HTTP 304 Not Modified support
- **CORS headers** for cross-origin requests
DPCalendar does not ship with a Web Services plugin. This plugin fills that gap by registering REST API routes for: ---
- **Events** — CRUD with date filtering, category scoping, and recurrence support ## Requirements
- **Calendars** — List and manage calendar categories
- **Locations** — List and manage event locations
## Install | Requirement | Version |
|---|---|
| **PHP** | 8.1+ |
| **Joomla** | 5.x or 6.x |
| **DPCalendar** | Required (component must be installed) |
1. Download the latest release ZIP ---
2. **System > Install > Extensions** in Joomla admin
3. Upload and install the ZIP
4. **System > Manage > Plugins** — enable **Web Services - DPCalendar**
## API Endpoints ## Installation
All endpoints require `Authorization: Bearer <token>`. 1. Download the latest release package from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases)
2. In Joomla Admin, go to **System > Install > Extensions**
3. Upload the `.zip` package
4. Navigate to **System > Plugins** and search for `"Web Services - DPCalendar"`
5. Enable the plugin
### Events See the [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/INSTALLATION) wiki page for detailed instructions.
| Method | Endpoint | Description | ---
|--------|----------|-------------|
| GET | `/v1/dpcalendar/events` | List events |
| GET | `/v1/dpcalendar/events/{id}` | Get event |
| POST | `/v1/dpcalendar/events` | Create event |
| PATCH | `/v1/dpcalendar/events/{id}` | Update event |
| DELETE | `/v1/dpcalendar/events/{id}` | Delete event |
### Calendars ## Authentication
| Method | Endpoint | Description | All API requests require a Joomla API token passed via the `Authorization` header:
|--------|----------|-------------|
| GET | `/v1/dpcalendar/calendars` | List calendars |
| GET | `/v1/dpcalendar/calendars/{id}` | Get calendar |
### Locations ```
Authorization: Bearer <your-joomla-api-token>
```
| Method | Endpoint | Description | Generate a token in **Joomla Admin > Users > Manage > [User] > Joomla API Token tab**.
|--------|----------|-------------|
| GET | `/v1/dpcalendar/locations` | List locations | ---
| GET | `/v1/dpcalendar/locations/{id}` | Get location |
| POST | `/v1/dpcalendar/locations` | Create location | ## Endpoints
| PATCH | `/v1/dpcalendar/locations/{id}` | Update location |
| DELETE | `/v1/dpcalendar/locations/{id}` | Delete location | Base path: `/api/index.php/v1`
### Events (8 endpoints)
| Method | Path | Description |
|---|---|---|
| `GET` | `/dpcalendar/events` | List events with filtering, sorting, pagination |
| `GET` | `/dpcalendar/events/{id}` | Get a single event by ID |
| `POST` | `/dpcalendar/events` | Create a new event |
| `POST` | `/dpcalendar/events/bulk` | Bulk create multiple events |
| `PATCH` | `/dpcalendar/events/{id}` | Update an existing event |
| `DELETE` | `/dpcalendar/events/{id}` | Trash an event (soft delete) |
| `GET` | `/dpcalendar/events/{id}/ical` | Export event as iCal (.ics) |
| `GET` | `/dpcalendar/events/{id}/occurrences` | List occurrences of a recurring event |
### Calendars (3 endpoints)
| Method | Path | Description |
|---|---|---|
| `GET` | `/dpcalendar/calendars` | List all calendars |
| `GET` | `/dpcalendar/calendars/{id}` | Get a single calendar by ID |
| `GET` | `/dpcalendar/calendars/{id}/ical` | Export calendar as iCal (.ics) |
### Locations (2 endpoints)
| Method | Path | Description |
|---|---|---|
| `GET` | `/dpcalendar/locations` | List all locations |
| `GET` | `/dpcalendar/locations/{id}` | Get a single location by ID |
### Bookings (2 endpoints)
| Method | Path | Description |
|---|---|---|
| `GET` | `/dpcalendar/bookings` | List all bookings |
| `GET` | `/dpcalendar/bookings/{id}` | Get a single booking (with tickets) |
### Tickets (2 endpoints)
| Method | Path | Description |
|---|---|---|
| `GET` | `/dpcalendar/tickets` | List all tickets |
| `GET` | `/dpcalendar/tickets/{id}` | Get a single ticket |
---
## Query Parameters
### Pagination
| Parameter | Type | Default | Description |
|---|---|---|---|
| `page[limit]` | integer | 20 | Results per page (max 100) |
| `page[offset]` | integer | 0 | Number of results to skip |
### Sorting
| Parameter | Type | Description |
|---|---|---|
| `sort` | string | Sort field. Prefix with `-` for descending. |
**Supported sort fields:** `id`, `title`, `start_date`, `end_date`, `catid`, `created`
Example: `?sort=-start_date` (newest first)
### Filtering
| Parameter | Type | Description |
|---|---|---|
| `filter[search]` | string | Search events by title or description |
| `filter[start_date]` | string | Events starting on or after this date (ISO 8601) |
| `filter[end_date]` | string | Events ending on or before this date (ISO 8601) |
| `filter[catid]` | integer | Filter by calendar/category ID |
| `filter[featured]` | integer | `1` = featured only, `0` = non-featured only |
| `filter[access]` | integer | Filter by Joomla access level |
| `filter[language]` | string | Filter by language tag (e.g., `en-GB`) |
### Field Selection
| Parameter | Type | Description |
|---|---|---|
| `fields[events]` | string | Comma-separated list of fields to return |
Example: `?fields[events]=id,title,start_date,end_date`
### Expansion
| Parameter | Type | Description |
|---|---|---|
| `expand` | string | Include related data. Supported: `locations` |
---
## Examples
### List upcoming events
```bash
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
"https://example.com/api/index.php/v1/dpcalendar/events?filter[start_date]=2026-01-01&sort=start_date&page[limit]=10"
```
### Get a single event
```bash
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
"https://example.com/api/index.php/v1/dpcalendar/events/42"
```
### Create an event
```bash
curl -s -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.api+json" \
-d '{
"title": "Monthly Meetup",
"catid": 8,
"start_date": "2026-06-15 18:00:00",
"end_date": "2026-06-15 20:00:00",
"description": "<p>Join us for the monthly meetup!</p>"
}' \
"https://example.com/api/index.php/v1/dpcalendar/events"
```
### Bulk create events
```bash
curl -s -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.api+json" \
-d '[
{"title": "Event A", "catid": 8, "start_date": "2026-07-01 10:00:00", "end_date": "2026-07-01 12:00:00"},
{"title": "Event B", "catid": 8, "start_date": "2026-07-02 10:00:00", "end_date": "2026-07-02 12:00:00"}
]' \
"https://example.com/api/index.php/v1/dpcalendar/events/bulk"
```
### Export calendar as iCal
```bash
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: text/calendar" \
"https://example.com/api/index.php/v1/dpcalendar/calendars/8/ical"
```
### Get recurring event occurrences
```bash
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
"https://example.com/api/index.php/v1/dpcalendar/events/42/occurrences"
```
### List events with location expansion
```bash
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
"https://example.com/api/index.php/v1/dpcalendar/events?expand=locations"
```
### ETag caching (conditional request)
```bash
# First request -- note the ETag in response headers
curl -sI \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
"https://example.com/api/index.php/v1/dpcalendar/events"
# Subsequent request -- returns 304 if unchanged
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/vnd.api+json" \
-H 'If-None-Match: "etag-value-from-previous-response"' \
"https://example.com/api/index.php/v1/dpcalendar/events"
```
---
## Documentation
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki), including:
| Page | Description |
|---|---|
| [Home](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/Home) | Overview and quick reference |
| [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/INSTALLATION) | Installation guide for Joomla 5.x/6.x |
| [API-Reference](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/API-Reference) | Complete endpoint documentation |
---
## License ## License
GPL-3.0-or-later see [LICENSE](LICENSE). This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> ---
## Maintainers *[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
[@jmiller](https://git.mokoconsulting.tech/jmiller)
## Revision History
| Date | Version | Author | Notes |
| --- | --- | --- | --- |
| 2026-04-26 | 1.0.0 | jmiller | Initial Web Services plugin for DPCalendar |
+237
View File
@@ -0,0 +1,237 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Automation.CI
# INGROUP: moko-platform.Automation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /automation/ci-issue-reporter.sh
# VERSION: 09.23.00
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
# Deduplicates by searching open issues with the "ci-auto" label
# whose title matches the gate. If a matching issue exists, a comment
# is appended instead of opening a duplicate.
# ============================================================================
set -euo pipefail
# ── Defaults ────────────────────────────────────────────────────────────────
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
REPO="${GITHUB_REPOSITORY:-}"
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
LABEL_NAME="ci-auto"
LABEL_COLOR="#e11d48"
GATE=""
DETAILS=""
SEVERITY="error"
WORKFLOW=""
# ── Parse arguments ─────────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
Required:
--gate CI gate name (e.g. "Code Quality", "Self-Health")
--details Human-readable failure description
Optional:
--severity "error" (default) or "warning"
--workflow Workflow name for the issue title
--repo owner/repo (default: \$GITHUB_REPOSITORY)
--run-url URL to the CI run (auto-detected from env)
--token Gitea API token (default: \$GITEA_TOKEN)
--url Gitea base URL (default: \$GITEA_URL)
EOF
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--gate) GATE="$2"; shift 2 ;;
--details) DETAILS="$2"; shift 2 ;;
--severity) SEVERITY="$2"; shift 2 ;;
--workflow) WORKFLOW="$2"; shift 2 ;;
--repo) REPO="$2"; shift 2 ;;
--run-url) RUN_URL="$2"; shift 2 ;;
--token) GITEA_TOKEN="$2"; shift 2 ;;
--url) GITEA_URL="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
API="${GITEA_URL}/api/v1/repos/${REPO}"
# ── Build title ─────────────────────────────────────────────────────────────
if [[ -n "$WORKFLOW" ]]; then
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
else
TITLE="[CI] ${GATE} failed"
fi
# ── Ensure label exists ─────────────────────────────────────────────────────
ensure_label() {
local exists
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null || echo "000")
if [[ "$exists" == "200" ]]; then
# Check if label already exists
local found
found=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null \
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
if [[ -z "$found" ]]; then
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/labels" \
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
> /dev/null 2>&1 || true
fi
fi
}
# ── Search for existing open issue ──────────────────────────────────────────
find_existing_issue() {
# URL-encode the gate name for the query
local query
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
local response
response=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
2>/dev/null || echo "[]")
# Extract the first matching issue number
echo "$response" \
| grep -oP '"number":\s*\K[0-9]+' \
| head -1
}
# ── Build issue body ────────────────────────────────────────────────────────
build_body() {
local severity_badge
if [[ "$SEVERITY" == "error" ]]; then
severity_badge="**Severity:** Error"
else
severity_badge="**Severity:** Warning"
fi
cat <<BODY
## CI Gate Failure: ${GATE}
${severity_badge}
**Workflow:** ${WORKFLOW:-unknown}
**Branch:** ${GITHUB_REF_NAME:-unknown}
**Commit:** \`${GITHUB_SHA:0:8}\`
**Run:** [View CI run](${RUN_URL})
### Details
${DETAILS}
### Resolution
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
---
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
BODY
}
# ── Build comment body (for existing issues) ────────────────────────────────
build_comment() {
cat <<COMMENT
### CI failure recurrence
**Branch:** ${GITHUB_REF_NAME:-unknown}
**Commit:** \`${GITHUB_SHA:0:8}\`
**Run:** [View CI run](${RUN_URL})
${DETAILS}
COMMENT
}
# ── Main ────────────────────────────────────────────────────────────────────
ensure_label
EXISTING=$(find_existing_issue)
if [[ -n "$EXISTING" ]]; then
# Append comment to existing issue
COMMENT_BODY=$(build_comment)
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
import sys, json
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${EXISTING}/comments" \
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
if [[ "$HTTP" == "201" ]]; then
echo "Commented on existing issue #${EXISTING}"
else
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
fi
else
# Create new issue
ISSUE_BODY=$(build_body)
ISSUE_JSON=$(python3 -c "
import sys, json
body = sys.stdin.read()
print(json.dumps({
'title': sys.argv[1],
'body': body,
'labels': []
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
# Create the issue
RESPONSE=$(curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues" \
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
if [[ -n "$ISSUE_NUM" ]]; then
# Apply label (separate call — more reliable across Gitea versions)
LABEL_ID=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null \
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
| head -1 || true)
if [[ -n "$LABEL_ID" ]]; then
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${ISSUE_NUM}/labels" \
-d "{\"labels\":[${LABEL_ID}]}" \
> /dev/null 2>&1 || true
fi
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
else
echo "WARNING: Failed to create issue"
echo "Response: ${RESPONSE}"
fi
fi
-41
View File
@@ -1,41 +0,0 @@
# Installation
## Prerequisites
- Joomla 5.x or 6.x
- DPCalendar 9.x+ installed and enabled
- PHP 8.1+
## Install from Release
1. Download the latest ZIP from Releases
2. In Joomla admin: **System > Install > Extensions**
3. Upload and install the ZIP
4. Go to **System > Manage > Plugins**
5. Search for "DPCalendar" and enable **Web Services - DPCalendar**
## Install from Source
```sh
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI.git
cd MokoDPCalendarAPI
```
Install the `src/` directory as a Joomla extension via Install from Folder or symlink.
## Verify
```sh
curl -s https://your-site.com/api/index.php/v1/dpcalendar/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/vnd.api+json"
```
## Troubleshooting
### "Resource not found"
- Ensure the plugin is enabled in Plugins manager
- Verify DPCalendar component is installed
### Empty responses
- Check API user has access to the calendars
+5 -4
View File
@@ -3,17 +3,17 @@
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
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
VERSION: 01.00.00 VERSION: 03.01.00
--> -->
<extension type="plugin" group="webservices" method="upgrade"> <extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<creationDate>2026-05-04</creationDate> <creationDate>2026-05-10</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting</copyright> <copyright>Copyright (C) 2026 Moko Consulting</copyright>
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>03.00.00</version> <version>03.02.00</version>
<description>Exposes DPCalendar events, calendars, and locations via the Joomla Web Services API</description> <description>Exposes DPCalendar events, calendars, and locations via the Joomla Web Services API</description>
<namespace path="src">Moko\Plugin\WebServices\MokoDPCalendarAPI</namespace> <namespace path="src">Moko\Plugin\WebServices\MokoDPCalendarAPI</namespace>
<files> <files>
@@ -24,3 +24,4 @@
<server type="extension" name="MokoDPCalendarAPI Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/raw/branch/main/updates.xml</server> <server type="extension" name="MokoDPCalendarAPI Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/raw/branch/main/updates.xml</server>
</updateservers> </updateservers>
</extension> </extension>
+109 -4
View File
@@ -6,7 +6,7 @@
* *
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
* PATH: /src/src/Extension/MokoDPCalendarAPI.php * PATH: /src/src/Extension/MokoDPCalendarAPI.php
* VERSION: 02.00.00 * VERSION: 03.01.00
* BRIEF: Plugin class — registers and handles DPCalendar API routes * BRIEF: Plugin class — registers and handles DPCalendar API routes
*/ */
@@ -46,9 +46,11 @@ final class MokoDPCalendarAPI extends CMSPlugin
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations', 'locations.displayList', [], $defaults)); $router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations', 'locations.displayList', [], $defaults));
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations/:id', 'locations.displayItem', ['id' => '(\d+)'], $defaults)); $router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations/:id', 'locations.displayItem', ['id' => '(\d+)'], $defaults));
// Bookings // Bookings + Tickets
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings', 'bookings.displayList', [], $defaults)); $router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings', 'bookings.displayList', [], $defaults));
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings/:id', 'bookings.displayItem', ['id' => '(\d+)'], $defaults)); $router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings/:id', 'bookings.displayItem', ['id' => '(\d+)'], $defaults));
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/tickets', 'tickets.displayList', [], $defaults));
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/tickets/:id', 'tickets.displayItem', ['id' => '(\d+)'], $defaults));
// ICS export (calendar-level) // ICS export (calendar-level)
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/calendars/:id/ical', 'calendars.ical', ['id' => '(\d+)'], $defaults)); $router->addRoute(new Route(['GET'], 'v1/dpcalendar/calendars/:id/ical', 'calendars.ical', ['id' => '(\d+)'], $defaults));
@@ -62,7 +64,7 @@ final class MokoDPCalendarAPI extends CMSPlugin
$controller = $input->get('controller', '', 'string'); $controller = $input->get('controller', '', 'string');
$task = $input->get('task', '', 'string'); $task = $input->get('task', '', 'string');
$validControllers = ['events', 'calendars', 'locations', 'bookings']; $validControllers = ['events', 'calendars', 'locations', 'bookings', 'tickets'];
if (!in_array($controller, $validControllers, true)) { if (!in_array($controller, $validControllers, true)) {
return; return;
} }
@@ -70,6 +72,14 @@ final class MokoDPCalendarAPI extends CMSPlugin
$id = $input->getInt('id', 0); $id = $input->getInt('id', 0);
$method = $app->input->getMethod(); $method = $app->input->getMethod();
// Handle CORS preflight
if ($method === 'OPTIONS') {
$this->setCorsHeaders($app);
$app->setHeader('Content-Length', '0', true);
$app->sendHeaders();
$app->close();
}
try { try {
$data = match (true) { $data = match (true) {
// Events // Events
@@ -95,11 +105,16 @@ final class MokoDPCalendarAPI extends CMSPlugin
$controller === 'bookings' && $method === 'GET' && !$id => $this->listBookings($input), $controller === 'bookings' && $method === 'GET' && !$id => $this->listBookings($input),
$controller === 'bookings' && $method === 'GET' && $id > 0 => $this->getBooking($id), $controller === 'bookings' && $method === 'GET' && $id > 0 => $this->getBooking($id),
// Tickets
$controller === 'tickets' && $method === 'GET' && !$id => $this->listTickets($input),
$controller === 'tickets' && $method === 'GET' && $id > 0 => $this->getTicket($id),
default => throw new \RuntimeException('Method not allowed', 405), default => throw new \RuntimeException('Method not allowed', 405),
}; };
// ICS responses bypass JSON // ICS responses bypass JSON
if (is_string($data)) { if (is_string($data)) {
$this->setCorsHeaders($app);
$app->setHeader('Content-Type', 'text/calendar; charset=utf-8', true); $app->setHeader('Content-Type', 'text/calendar; charset=utf-8', true);
$app->setHeader('Content-Disposition', 'attachment; filename="export.ics"', true); $app->setHeader('Content-Disposition', 'attachment; filename="export.ics"', true);
$app->sendHeaders(); $app->sendHeaders();
@@ -116,16 +131,41 @@ final class MokoDPCalendarAPI extends CMSPlugin
// ── Response helpers ───────────────────────────────────────────── // ── Response helpers ─────────────────────────────────────────────
private function setCorsHeaders($app): void
{
$app->setHeader('Access-Control-Allow-Origin', '*', true);
$app->setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS', true);
$app->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Joomla-Token', true);
$app->setHeader('Access-Control-Max-Age', '86400', true);
}
private function sendResponse($app, array $data): void private function sendResponse($app, array $data): void
{ {
$this->setCorsHeaders($app);
$app->setHeader('Content-Type', 'application/vnd.api+json', true); $app->setHeader('Content-Type', 'application/vnd.api+json', true);
// ETag for caching
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$etag = '"' . md5($json) . '"';
$app->setHeader('ETag', $etag, true);
$app->setHeader('Cache-Control', 'private, max-age=60', true);
// Return 304 if client has current version
$ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
if ($ifNoneMatch === $etag) {
http_response_code(304);
$app->sendHeaders();
$app->close();
}
$app->sendHeaders(); $app->sendHeaders();
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); echo $json;
$app->close(); $app->close();
} }
private function sendError($app, int $code, string $message): void private function sendError($app, int $code, string $message): void
{ {
$this->setCorsHeaders($app);
http_response_code($code); http_response_code($code);
$app->setHeader('Content-Type', 'application/vnd.api+json', true); $app->setHeader('Content-Type', 'application/vnd.api+json', true);
$app->sendHeaders(); $app->sendHeaders();
@@ -232,6 +272,18 @@ final class MokoDPCalendarAPI extends CMSPlugin
->bind(':featured', $featVal, ParameterType::INTEGER); ->bind(':featured', $featVal, ParameterType::INTEGER);
} }
$accessLevel = $input->getInt('access', 0);
if ($accessLevel) {
$query->where($db->quoteName('e.access') . ' = :access')
->bind(':access', $accessLevel, ParameterType::INTEGER);
}
$lang = $input->getString('language', '');
if ($lang) {
$query->where('(' . $db->quoteName('e.language') . ' = :lang OR ' . $db->quoteName('e.language') . ' = ' . $db->quote('*') . ')')
->bind(':lang', $lang);
}
// Count total // Count total
$countQuery = clone $query; $countQuery = clone $query;
$countQuery->clear('select')->clear('order')->select('COUNT(*)'); $countQuery->clear('select')->clear('order')->select('COUNT(*)');
@@ -742,6 +794,59 @@ final class MokoDPCalendarAPI extends CMSPlugin
return ['data' => $result]; return ['data' => $result];
} }
// ── Tickets ──────────────────────────────────────────────────────
private function listTickets($input): array
{
$db = $this->getDb();
[$limit, $offset] = $this->getPagination($input);
$query = $db->getQuery(true)
->select('t.id, t.uid, t.booking_id, t.event_id, t.name, t.email, t.state, t.price, t.created')
->from($db->quoteName('#__dpcalendar_tickets', 't'))
->order('t.created DESC');
$eventId = $input->getInt('event_id', 0);
if ($eventId) {
$query->where($db->quoteName('t.event_id') . ' = :event_id')
->bind(':event_id', $eventId, ParameterType::INTEGER);
}
$bookingId = $input->getInt('booking_id', 0);
if ($bookingId) {
$query->where($db->quoteName('t.booking_id') . ' = :booking_id')
->bind(':booking_id', $bookingId, ParameterType::INTEGER);
}
$countQuery = clone $query;
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
$total = (int) $db->setQuery($countQuery)->loadResult();
$db->setQuery($query, $offset, $limit);
$items = $db->loadAssocList() ?: [];
return [
'data' => $items,
'meta' => ['total-items' => $total, 'limit' => $limit, 'offset' => $offset],
];
}
private function getTicket(int $id): array
{
$db = $this->getDb();
$query = $db->getQuery(true)
->select('t.*')
->from($db->quoteName('#__dpcalendar_tickets', 't'))
->where($db->quoteName('t.id') . ' = :id')
->bind(':id', $id, ParameterType::INTEGER);
$result = $db->setQuery($query)->loadAssoc();
if (!$result) {
throw new \RuntimeException('Ticket not found', 404);
}
return ['data' => $result];
}
// ── ICS/iCal Export ────────────────────────────────────────────── // ── ICS/iCal Export ──────────────────────────────────────────────
private function eventToIcal(int $id): string private function eventToIcal(int $id): string
+36 -36
View File
@@ -1,97 +1,97 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- 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
VERSION: 02.00.00 VERSION: 03.02.00
--> -->
<updates> <updates>
<update> <update>
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description> <description>Moko Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element> <element>mokodpcalendarapi</element>
<type>plugin</type> <type>plugin</type>
<version>02.00.00</version> <version>03.02.00</version>
<client>site</client> <client>site</client>
<folder>webservices</folder> <folder>webservices</folder>
<tags><tag>development</tag></tags> <tags><tag>development</tag></tags>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl> <infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-02.00.00.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
</downloads> </downloads>
<sha256>94e05c821073273b9a4dae17b4e89aab97e9d32a5b397d38e19c1ae75ebb9f8b</sha256> <sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" /> <targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<update> <update>
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description> <description>Moko Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element> <element>mokodpcalendarapi</element>
<type>plugin</type> <type>plugin</type>
<version>02.00.00</version> <version>03.02.00</version>
<client>site</client> <client>site</client>
<folder>webservices</folder> <folder>webservices</folder>
<tags><tag>alpha</tag></tags> <tags><tag>alpha</tag></tags>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl> <infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-02.00.00.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
</downloads> </downloads>
<sha256>94e05c821073273b9a4dae17b4e89aab97e9d32a5b397d38e19c1ae75ebb9f8b</sha256> <sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" /> <targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<update> <update>
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description> <description>Moko Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element> <element>mokodpcalendarapi</element>
<type>plugin</type> <type>plugin</type>
<version>02.00.00</version> <version>03.02.00</version>
<client>site</client> <client>site</client>
<folder>webservices</folder> <folder>webservices</folder>
<tags><tag>beta</tag></tags> <tags><tag>beta</tag></tags>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl> <infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-02.00.00.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
</downloads> </downloads>
<sha256>94e05c821073273b9a4dae17b4e89aab97e9d32a5b397d38e19c1ae75ebb9f8b</sha256> <sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" /> <targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<update> <update>
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description> <description>Moko Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element> <element>mokodpcalendarapi</element>
<type>plugin</type> <type>plugin</type>
<version>02.00.00</version> <version>03.02.00</version>
<client>site</client> <client>site</client>
<folder>webservices</folder> <folder>webservices</folder>
<tags><tag>rc</tag></tags> <tags><tag>rc</tag></tags>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl> <infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-02.00.00.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
</downloads> </downloads>
<sha256>94e05c821073273b9a4dae17b4e89aab97e9d32a5b397d38e19c1ae75ebb9f8b</sha256> <sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" /> <targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>
<update> <update>
<name>Web Services - DPCalendar API</name> <name>Moko Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description> <description>Moko Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element> <element>mokodpcalendarapi</element>
<type>plugin</type> <type>plugin</type>
<version>02.00.00</version> <version>03.02.00</version>
<client>site</client> <client>site</client>
<folder>webservices</folder> <folder>webservices</folder>
<tags><tag>stable</tag></tags> <tags><tag>stable</tag></tags>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl> <infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-02.00.00.zip</downloadurl> <downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
</downloads> </downloads>
<sha256>94e05c821073273b9a4dae17b4e89aab97e9d32a5b397d38e19c1ae75ebb9f8b</sha256> <sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" /> <targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer> <maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl> <maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update> </update>