158 Commits

Author SHA1 Message Date
jmiller 7bd40e3b48 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:16:53 +00:00
jmiller 756bde66d7 chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:16:52 +00:00
jmiller cd318b2e85 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:16:52 +00:00
jmiller 63baf02135 chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:16:52 +00:00
jmiller 22926b6d49 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:16:51 +00:00
jmiller 49372da0ac chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:16:51 +00:00
jmiller 1d2350f5ae chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:16:50 +00:00
jmiller 28330d5daa chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:16:50 +00:00
jmiller 4705e2c416 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:16:49 +00:00
jmiller 116b39d980 chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:16:49 +00:00
jmiller c69c1fac10 chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:16:48 +00:00
jmiller 721e18fbae chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:16:48 +00:00
jmiller cbf5f0558e chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:16:48 +00:00
jmiller 5bb789532c chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:16:47 +00:00
jmiller 8e216dd0dd chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:16:47 +00:00
jmiller 0caeb62668 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:16:46 +00:00
jmiller 1962899c01 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:16:46 +00:00
jmiller a44363ba28 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:16:46 +00:00
jmiller e8018ebdd4 chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:16:45 +00:00
jmiller c0747ae8b4 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:16:45 +00:00
jmiller 29e96919eb chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:16:44 +00:00
jmiller 079b199f24 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:16:44 +00:00
jmiller d2902f6737 chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:11:05 +00:00
jmiller d3ab78309b chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:11:04 +00:00
jmiller ad60cfd56c chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:11:04 +00:00
jmiller 238f9dba27 chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:11:03 +00:00
jmiller 3869bd8a76 chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:11:03 +00:00
jmiller e4ed99eca4 chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:11:03 +00:00
jmiller cb1011f1dc chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:11:02 +00:00
jmiller 21ac242019 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:11:02 +00:00
jmiller 984ae9f606 chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:11:01 +00:00
jmiller 695a3acaf1 chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:11:01 +00:00
jmiller 8b891160ef chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:11:01 +00:00
jmiller 408fe0259b chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:11:00 +00:00
jmiller 6ec115eab1 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:11:00 +00:00
jmiller 30f3a6a02e chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:10:59 +00:00
jmiller 18be26e02e chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:10:59 +00:00
jmiller bb2c88f485 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:10:59 +00:00
jmiller 81abd3557e chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:10:58 +00:00
jmiller eb9df5496a chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:10:58 +00:00
jmiller 9ed7c763ff chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:10:57 +00:00
jmiller 9e87eb9bec chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:10:57 +00:00
jmiller c9271e4977 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:10:57 +00:00
jmiller ebb1f5f4c9 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:10:56 +00:00
jmiller 793b52600f chore: remove .mokogitea/.moko-platform [skip ci] 2026-05-12 19:09:04 +00:00
jmiller adcf95e82e chore: force-sync .mokogitea/ISSUE_TEMPLATE/version.md [skip ci] 2026-05-12 19:09:03 +00:00
jmiller 9091ba950b chore: force-sync .mokogitea/ISSUE_TEMPLATE/security.md [skip ci] 2026-05-12 19:09:03 +00:00
jmiller 916e385345 chore: force-sync .mokogitea/ISSUE_TEMPLATE/rfc.md [skip ci] 2026-05-12 19:09:03 +00:00
jmiller 87d4a9ec8a chore: force-sync .mokogitea/ISSUE_TEMPLATE/question.md [skip ci] 2026-05-12 19:09:02 +00:00
jmiller 7a1b5d63aa chore: force-sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md [skip ci] 2026-05-12 19:09:02 +00:00
jmiller d83619377f chore: force-sync .mokogitea/ISSUE_TEMPLATE/feature_request.md [skip ci] 2026-05-12 19:09:01 +00:00
jmiller 32ba67378b chore: force-sync .mokogitea/ISSUE_TEMPLATE/documentation.md [skip ci] 2026-05-12 19:09:01 +00:00
jmiller be07a35788 chore: force-sync .mokogitea/ISSUE_TEMPLATE/config.yml [skip ci] 2026-05-12 19:09:01 +00:00
jmiller 97fe1b99dd chore: force-sync .mokogitea/ISSUE_TEMPLATE/bug_report.md [skip ci] 2026-05-12 19:09:00 +00:00
jmiller e21103fb8c chore: force-sync .mokogitea/ISSUE_TEMPLATE/adr.md [skip ci] 2026-05-12 19:09:00 +00:00
jmiller 7d38f0aa91 chore: force-sync .mokogitea/workflows/update-server.yml [skip ci] 2026-05-12 19:08:59 +00:00
jmiller fb2e97a8ed chore: force-sync .mokogitea/workflows/security-audit.yml [skip ci] 2026-05-12 19:08:59 +00:00
jmiller 8ab92c0f24 chore: force-sync .mokogitea/workflows/repo-health.yml [skip ci] 2026-05-12 19:08:59 +00:00
jmiller f9b6177a78 chore: force-sync .mokogitea/workflows/pre-release.yml [skip ci] 2026-05-12 19:08:58 +00:00
jmiller 800876bd71 chore: force-sync .mokogitea/workflows/pr-check.yml [skip ci] 2026-05-12 19:08:58 +00:00
jmiller 43d7e8c973 chore: force-sync .mokogitea/workflows/notify.yml [skip ci] 2026-05-12 19:08:58 +00:00
jmiller 3d7b7845b2 chore: force-sync .mokogitea/workflows/gitleaks.yml [skip ci] 2026-05-12 19:08:57 +00:00
jmiller 81ad188b69 chore: force-sync .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-05-12 19:08:57 +00:00
jmiller 3bd725b82e chore: force-sync .mokogitea/workflows/cleanup.yml [skip ci] 2026-05-12 19:08:56 +00:00
jmiller ac0f9cb3b7 chore: force-sync .mokogitea/workflows/ci-joomla.yml [skip ci] 2026-05-12 19:08:56 +00:00
jmiller f403fd6a53 chore: force-sync .mokogitea/workflows/cascade-dev.yml [skip ci] 2026-05-12 19:08:56 +00:00
jmiller 3d8808d241 chore: force-sync .mokogitea/workflows/auto-release.yml [skip ci] 2026-05-12 19:08:55 +00:00
jmiller 1b5c4a8f33 chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:46:39 +00:00
jmiller 7691739085 chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:46:39 +00:00
jmiller 750c61812d chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:46:38 +00:00
jmiller b7b5d78929 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:46:38 +00:00
jmiller 35d83d7a03 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:46:38 +00:00
jmiller 3c4f135f56 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:46:37 +00:00
jmiller a5ecebe782 chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:46:37 +00:00
jmiller bc321218a0 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:46:37 +00:00
jmiller 737c2e2a77 chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:46:36 +00:00
jmiller 34f1c95d68 chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:46:36 +00:00
jmiller 6337622bad chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:46:35 +00:00
jmiller d96020323e chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:46:35 +00:00
jmiller 8cb34f227e chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:46:35 +00:00
jmiller 3573fa9c76 chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:46:34 +00:00
jmiller e79e190361 chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:46:34 +00:00
jmiller 29568997d6 chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:46:34 +00:00
jmiller 6e98afe1ea chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:46:33 +00:00
jmiller dec27343c2 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:46:33 +00:00
jmiller 7e95020f23 chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:46:33 +00:00
jmiller 34c0bb97ee chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:46:32 +00:00
jmiller 3e18f25ce1 chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:46:32 +00:00
jmiller a2a8342c72 chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:46:31 +00:00
jmiller ac8b6ed0cd chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 18:37:43 +00:00
jmiller f5f0146ae4 chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 18:37:42 +00:00
jmiller 2b684672f4 chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 18:37:42 +00:00
jmiller 6c4bb7c0ba chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 18:37:41 +00:00
jmiller 539ea699e6 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 18:37:41 +00:00
jmiller 82a00fc30a chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 18:37:41 +00:00
jmiller 3cbe9d9c8f chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 18:37:40 +00:00
jmiller d1ad2b390e chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 18:37:40 +00:00
jmiller 84fd1e29c2 chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 18:37:40 +00:00
jmiller e94fa5e4ac chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 18:37:39 +00:00
jmiller ab8759c84e chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 18:37:39 +00:00
jmiller 6cd6e2819c chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 18:37:38 +00:00
jmiller c8b0009ce4 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 18:37:38 +00:00
jmiller e38d75271e chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 18:37:38 +00:00
jmiller ac5b4ef3ac chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 18:37:37 +00:00
jmiller d367496858 chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 18:37:37 +00:00
jmiller bde7ac083d chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 18:37:37 +00:00
jmiller cbcaf39b8f chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 18:37:36 +00:00
jmiller f742fbd90b chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 18:37:36 +00:00
jmiller 3b0c71bcd7 chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 18:37:35 +00:00
jmiller 13572dd8c4 chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 18:37:35 +00:00
jmiller 83039c55a8 chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 18:37:35 +00:00
jmiller 77452f4c11 chore: sync .mokogitea/workflows/update-server.yml from template [skip ci] 2026-05-12 05:30:46 +00:00
jmiller d2e9852fbf chore: sync .mokogitea/workflows/security-audit.yml from template [skip ci] 2026-05-12 05:30:46 +00:00
jmiller 4324309465 chore: sync .mokogitea/workflows/repo-health.yml from template [skip ci] 2026-05-12 05:30:45 +00:00
jmiller 7561fb9e1e chore: sync .mokogitea/workflows/pre-release.yml from template [skip ci] 2026-05-12 05:30:45 +00:00
jmiller e48b10c868 chore: sync .mokogitea/workflows/pr-check.yml from template [skip ci] 2026-05-12 05:30:44 +00:00
jmiller 633af3d885 chore: sync .mokogitea/workflows/notify.yml from template [skip ci] 2026-05-12 05:30:44 +00:00
jmiller 21f958ae56 chore: sync .mokogitea/workflows/gitleaks.yml from template [skip ci] 2026-05-12 05:30:44 +00:00
jmiller da2ec86c61 chore: sync .mokogitea/workflows/deploy-manual.yml from template [skip ci] 2026-05-12 05:30:43 +00:00
jmiller 7200b8c94e chore: sync .mokogitea/workflows/cleanup.yml from template [skip ci] 2026-05-12 05:30:43 +00:00
jmiller ece9195d9f chore: sync .mokogitea/workflows/ci-joomla.yml from template [skip ci] 2026-05-12 05:30:43 +00:00
jmiller ec960ae8ca chore: sync .mokogitea/workflows/cascade-dev.yml from template [skip ci] 2026-05-12 05:30:42 +00:00
jmiller c3ba382fda chore: sync .mokogitea/workflows/auto-release.yml from template [skip ci] 2026-05-12 05:30:42 +00:00
jmiller 30d33c2275 chore: sync .mokogitea/ISSUE_TEMPLATE/version.md from template [skip ci] 2026-05-12 05:30:42 +00:00
jmiller 4e52463fee chore: sync .mokogitea/ISSUE_TEMPLATE/security.md from template [skip ci] 2026-05-12 05:30:41 +00:00
jmiller d73c45089c chore: sync .mokogitea/ISSUE_TEMPLATE/rfc.md from template [skip ci] 2026-05-12 05:30:41 +00:00
jmiller d25c42e0a1 chore: sync .mokogitea/ISSUE_TEMPLATE/question.md from template [skip ci] 2026-05-12 05:30:41 +00:00
jmiller cdfc404f48 chore: sync .mokogitea/ISSUE_TEMPLATE/joomla_issue.md from template [skip ci] 2026-05-12 05:30:40 +00:00
jmiller 33018f9892 chore: sync .mokogitea/ISSUE_TEMPLATE/feature_request.md from template [skip ci] 2026-05-12 05:30:40 +00:00
jmiller 1379e48129 chore: sync .mokogitea/ISSUE_TEMPLATE/documentation.md from template [skip ci] 2026-05-12 05:30:40 +00:00
jmiller 4bbf9aedd8 chore: sync .mokogitea/ISSUE_TEMPLATE/config.yml from template [skip ci] 2026-05-12 05:30:39 +00:00
jmiller 8bb86c1ac4 chore: sync .mokogitea/ISSUE_TEMPLATE/bug_report.md from template [skip ci] 2026-05-12 05:30:39 +00:00
jmiller 9afb2bd4d6 chore: sync .mokogitea/ISSUE_TEMPLATE/adr.md from template [skip ci] 2026-05-12 05:30:38 +00:00
jmiller 7b21df1b6a chore: sync .mokogitea/.moko-platform from template [skip ci] 2026-05-12 05:30:38 +00:00
jmiller 5867ee7917 chore: remove .gitea/workflows/update-server.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:28 +00:00
jmiller f8a9d6e425 chore: move .gitea/workflows/update-server.yml to .mokogitea/update-server.yml [skip ci] 2026-05-12 04:58:27 +00:00
jmiller 88ad87217c chore: remove .gitea/workflows/security-audit.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:27 +00:00
jmiller 648f7c76cf chore: move .gitea/workflows/security-audit.yml to .mokogitea/security-audit.yml [skip ci] 2026-05-12 04:58:27 +00:00
jmiller 1caadcea88 chore: remove .gitea/workflows/repo-health.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:26 +00:00
jmiller c8e36dead9 chore: move .gitea/workflows/repo-health.yml to .mokogitea/repo-health.yml [skip ci] 2026-05-12 04:58:26 +00:00
jmiller 0068debb9b chore: remove .gitea/workflows/pre-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:25 +00:00
jmiller 0aa728ace6 chore: move .gitea/workflows/pre-release.yml to .mokogitea/pre-release.yml [skip ci] 2026-05-12 04:58:25 +00:00
jmiller 545e70a65f chore: remove .gitea/workflows/pr-check.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:25 +00:00
jmiller 858b498a20 chore: move .gitea/workflows/pr-check.yml to .mokogitea/pr-check.yml [skip ci] 2026-05-12 04:58:24 +00:00
jmiller aef378fa77 chore: remove .gitea/workflows/notify.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:24 +00:00
jmiller 4dc7f34f8d chore: move .gitea/workflows/notify.yml to .mokogitea/notify.yml [skip ci] 2026-05-12 04:58:24 +00:00
jmiller 0f08288b0e chore: remove .gitea/workflows/deploy-manual.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:23 +00:00
jmiller cc5fd83288 chore: move .gitea/workflows/deploy-manual.yml to .mokogitea/deploy-manual.yml [skip ci] 2026-05-12 04:58:23 +00:00
jmiller 766cb58d47 chore: remove .gitea/workflows/cleanup.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:22 +00:00
jmiller 49f1faf191 chore: move .gitea/workflows/cleanup.yml to .mokogitea/cleanup.yml [skip ci] 2026-05-12 04:58:22 +00:00
jmiller d13135d479 chore: remove .gitea/workflows/ci-joomla.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:22 +00:00
jmiller d24be9e8d6 chore: move .gitea/workflows/ci-joomla.yml to .mokogitea/ci-joomla.yml [skip ci] 2026-05-12 04:58:21 +00:00
jmiller 336af78c06 chore: remove .gitea/workflows/auto-release.yml (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:21 +00:00
jmiller a933bafe2a chore: move .gitea/workflows/auto-release.yml to .mokogitea/auto-release.yml [skip ci] 2026-05-12 04:58:21 +00:00
jmiller 9bda29eae7 chore: remove .gitea/.mokostandards (moved to .mokogitea/) [skip ci] 2026-05-12 04:58:20 +00:00
jmiller c0e2612a2b chore: move .gitea/.mokostandards to .mokogitea/.mokostandards [skip ci] 2026-05-12 04:58:20 +00:00
gitea-actions[bot] e22a8a4d0a chore(release): ZIP + tar.gz for 03.00.00 [skip ci] 2026-05-04 18:55:24 +00:00
gitea-actions[bot] d1f94d297f chore(release): build 03.00.00 [skip ci] 2026-05-04 18:55:23 +00:00
18 changed files with 216 additions and 2389 deletions
-59
View File
@@ -1,59 +0,0 @@
{
"permissions": {
"allow": [
"Bash(cd:*)",
"mcp__joomla-api__joomla_plugins_list",
"mcp__gitea-moko__get_repository_tree",
"mcp__joomla-api__joomla_api_request",
"Bash(curl:*)",
"mcp__gitea-moko__search_repos",
"Bash(TOKEN=\"29367101e6edf28375e0a03cef30a7dfc7eb2186\"\ncurl -sS -H \"Authorization: token $TOKEN\" \"https://git.mokoconsulting.tech/api/v1/repos/MokoConsulting/MokoOnyx/contents/.gitea/workflows/release.yml?ref=main\" | head -200)",
"mcp__gitea-moko__list_releases",
"mcp__gitea-moko__delete_release",
"mcp__gitea-moko__get_latest_release",
"mcp__gitea-moko__actions_run_read",
"Read(//a/MokoStandards-API/.gitea/**)",
"Read(//a/MokoStandards-API/**)",
"Read(//a/MokoStandards-Template-Joomla-Template/.gitea/workflows/**)",
"Read(//a/MokoStandards-Template-Joomla-Module/.gitea/workflows/**)",
"Read(//a/MokoStandards-Template-Joomla-Component/.gitea/workflows/**)",
"Read(//a/MokoStandards-Template-Joomla-Package/.gitea/workflows/**)",
"Read(//a/MokoStandards-Template-Joomla-Library/.gitea/workflows/**)",
"Read(//a/MokoJGDPC/.gitea/workflows/**)",
"Read(//a/MokoCassiopeia/.gitea/workflows/**)",
"Read(//a/MokoJoomHero/.gitea/workflows/**)",
"Read(//a/MokoJoomTOS/.gitea/workflows/**)",
"Read(//a/MokoWaaS/.gitea/workflows/**)",
"Read(//a/MokoWaaSAnnounce/.gitea/workflows/**)",
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce\"\n\nfor REPO in $REPOS; do\n echo \"=== $REPO ===\"\n cd \"A:/$REPO\"\n git pull --rebase origin main 2>&1 | tail -1\n rm -f .gitea/workflows/*.yml\n cp A:/MokoOnyx/.gitea/workflows/*.yml .gitea/workflows/\n git add .gitea/workflows/\n if git diff --cached --quiet; then\n echo \" No changes\"\n else\n git commit -m \"feat: expand workflow suite \\(10 workflows from MokoOnyx\\)\n\nCo-Authored-By: Claude Opus 4.6 \\(1M context\\) <noreply@anthropic.com>\"\n git push origin main 2>&1 | tail -1\n fi\n echo \"\"\ndone)",
"mcp__gitea-moko__actions_config_read",
"mcp__gitea-moko__actions_config_write",
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla-Plugin MokoStandards-Template-Joomla-Template MokoStandards-Template-Joomla-Module MokoStandards-Template-Joomla-Component MokoStandards-Template-Joomla-Package MokoStandards-Template-Joomla-Library MokoStandards-API\"\n\nfor REPO in $REPOS; do\n echo \"=== $REPO ===\"\n cd \"A:/$REPO\"\n git pull --rebase origin main 2>&1 | tail -1\n cp A:/MokoOnyx/.gitea/workflows/pre-release.yml .gitea/workflows/pre-release.yml\n git add .gitea/workflows/pre-release.yml\n if git diff --cached --quiet; then\n echo \" No changes\"\n else\n git commit -m \"feat: add pre-release workflow for manual dev/alpha/beta/rc builds\n\nCo-Authored-By: Claude Opus 4.6 \\(1M context\\) <noreply@anthropic.com>\"\n git push origin main 2>&1 | tail -1\n fi\n echo \"\"\ndone)",
"Bash(tail -1 rm .gitea/workflows/deploy.yml git add .gitea/workflows/deploy.yml git commit -m \"chore: remove auto-deploy workflow \\(deploy is manual only\\):*)",
"mcp__gitea-moko__pull_request_write",
"mcp__gitea-moko__actions_run_write",
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla-Plugin MokoStandards-Template-Joomla-Template MokoStandards-Template-Joomla-Module MokoStandards-Template-Joomla-Component MokoStandards-Template-Joomla-Package MokoStandards-Template-Joomla-Library MokoStandards-API\" __NEW_LINE_0c05a93337c7dba4__ for REPO in $REPOS)",
"Bash(do cd:*)",
"Bash(then echo \"$REPO: no changes\" else git commit -m \"fix: add patch version bump to pre-release workflow:*)",
"Bash(for REPO in client-clarksvillefurs client-kiddieland)",
"Bash(do echo:*)",
"Read(//a/MokoStandards-Template-Client/.gitea/workflows/**)",
"Bash(then echo \" No changes\" else git commit -m \"feat: add standard client workflow suite + media sync:*)",
"Bash(for:*)",
"mcp__gitea-moko__create_repo",
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_ef3af20e9774ebe9__ for REPO in $REPOS)",
"Bash(then echo \"$REPO: no changes\" else git commit -m \"fix: version bump logic — stable=minor, pre-release=patch:*)",
"Bash(then continue fi git commit -m \"fix: version bump logic — pre-release=patch with rollover:*)",
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_a82c045e0ae69651__ for REPO in $REPOS)",
"Bash(then continue:*)",
"Bash(fi git commit -m \"fix: stable release = major version bump \\(XX+1.00.00\\):*)",
"mcp__gitea-moko__get_release",
"Bash(ALL_REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_fbd28532f2d35636__ for REPO in $ALL_REPOS)",
"Bash(fi git commit -m \"fix: include element name in stable release title:*)"
]
},
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [
"joomla-api"
]
}
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
<?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>
+25 -192
View File
@@ -117,8 +117,8 @@ jobs:
echo "Version: $VERSION (patch — platform version + badges only)"
fi
# -- STEP 1b: Bump minor version (stable = minor bump, reset patch) ------
- name: "Step 1b: Bump minor version for stable release"
# -- STEP 1b: Bump major version (stable = major bump, reset minor+patch) -
- name: "Step 1b: Bump major version for stable release"
if: steps.version.outputs.skip != 'true'
id: bump
run: |
@@ -126,19 +126,14 @@ jobs:
[ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1)))
MINOR=$((10#$(echo "$CURRENT" | cut -d. -f2)))
# Minor bump, reset patch. Rollover if minor > 99
MINOR=$((MINOR + 1))
if [ $MINOR -gt 99 ]; then
MINOR=0
MAJOR=$((MAJOR + 1))
fi
# Major bump, reset minor and patch
MAJOR=$((MAJOR + 1))
VERSION=$(printf "%02d.%02d.00" $MAJOR $MINOR)
VERSION=$(printf "%02d.00.00" $MAJOR)
TODAY=$(date +%Y-%m-%d)
echo "Stable bump: ${CURRENT} → ${VERSION} (minor)"
echo "Stable bump: ${CURRENT} → ${VERSION} (major)"
# Update README.md
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
@@ -151,22 +146,13 @@ jobs:
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST"
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
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
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 add -A
git diff --cached --quiet || {
git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]"
git commit -m "chore(version): bump ${CURRENT} → ${VERSION} (major) [skip ci]"
git push origin HEAD:main 2>&1
}
@@ -320,7 +306,6 @@ jobs:
# -- STEP 5: Write updates.xml (Joomla update server) ---------------------
- name: "Step 5: Write updates.xml"
id: updates
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
@@ -344,44 +329,20 @@ jobs:
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)
# 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
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Derive element if not in manifest:
# 1. plugin="xxx" attribute (plugins)
# 2. module="xxx" attribute (modules)
# 3. XML filename (components, packages)
# 4. Repo name fallback (templates, anything else)
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas)
# 2. Fall back to repo name (lowercased)
if [ -z "$EXT_ELEMENT" ]; then
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:]')
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
# If filename is generic (templateDetails, manifest), use repo name
case "$FNAME" in
templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
*) EXT_ELEMENT="$FNAME" ;;
case "$EXT_ELEMENT" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
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>
CLIENT_TAG=""
@@ -408,18 +369,7 @@ jobs:
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi
# 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"
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
# -- Build update entry for a given stability tag
@@ -514,32 +464,21 @@ jobs:
MAJOR="${{ steps.version.outputs.major }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Reuse metadata from Step 5 (single source of truth)
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
EXT_NAME="${{ steps.updates.outputs.ext_name }}"
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
# Fallbacks if Step 5 was skipped
if [ -z "$EXT_ELEMENT" ]; then
# Auto-detect extension element for release naming
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
EXT_ELEMENT=""
if [ -n "$MANIFEST" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -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
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
# 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})"
RELEASE_NAME="${EXT_ELEMENT} ${VERSION} (stable)"
# Delete existing release if present (overwrite, not append)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
@@ -589,28 +528,9 @@ jobs:
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0
# Reuse element from Step 5, with same fallback chain
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
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"
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz"
# -- Build install packages from src/ ----------------------------
SOURCE_DIR="src"
@@ -750,73 +670,6 @@ jobs:
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
# -- 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) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
@@ -906,26 +759,6 @@ jobs:
done
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 --------------------------------------------------------------
- name: Pipeline Summary
if: always()
-213
View File
@@ -1,213 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.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
-73
View File
@@ -375,76 +375,3 @@ jobs:
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.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
@@ -1,96 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-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
-1
View File
@@ -18,7 +18,6 @@ on:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
- "Cascade Main → Dev"
types:
- completed
-90
View File
@@ -1,90 +0,0 @@
# 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
+1 -23
View File
@@ -278,7 +278,7 @@ jobs:
f.write(content)
PYEOF
# Commit and push to current branch
# Commit and push
if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
@@ -287,28 +287,6 @@ jobs:
git push origin HEAD 2>&1 || echo "WARNING: push failed"
fi
- name: "Sync updates.xml to all branches"
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)"
continue-on-error: true
run: |
+1 -1
View File
@@ -64,7 +64,7 @@ env:
# File / directory variables
DOCS_INDEX: docs/docs-index.md
SCRIPT_DIR: scripts
WORKFLOWS_DIR: .gitea/workflows
WORKFLOWS_DIR: .github/workflows
SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-193
View File
@@ -1,193 +0,0 @@
# 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
-24
View File
@@ -12,30 +12,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [03.01.00] — 2026-05-09
### Added
- Bookings API: list, get via `/v1/dpcalendar/bookings`
- Tickets API: list, get via `/v1/dpcalendar/tickets`
- ICS/iCal export: single event and full calendar export
- Recurring event occurrence expansion with RRULE support
- Bulk event creation via `POST /v1/dpcalendar/events/bulk`
- Calendar-level iCal export via `/v1/dpcalendar/calendars/{id}/ical`
- CORS support with preflight handling
- ETag caching for conditional GET requests
- Location expansion on events via `?expand=locations`
- Advanced filtering: date ranges, featured, access level, language
- Sorting and field selection on all list endpoints
- Search across event titles and descriptions
- Pagination with total count metadata
### Changed
- Version aligned across manifest and source files
- Production-ready error handling with proper HTTP status codes
### Fixed
- Version header consistency between manifest.xml and PHP source
## [01.00.00] — 2026-04-26
### Added
+71 -246
View File
@@ -1,269 +1,94 @@
<!-- 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
Joomla Web Services plugin exposing **18 REST endpoints** for DPCalendar events, calendars, locations, bookings, and tickets.
A Joomla 5/6 Web Services plugin that exposes DPCalendar events, calendars, and locations through the Joomla REST API (`/api/index.php/v1`).
![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)
Enables AI assistants (via joomla-api-mcp) and external integrations to create, read, update, and delete DPCalendar content programmatically.
---
## Table of Contents
## Features
- [Background](#background)
- [Install](#install)
- [API Endpoints](#api-endpoints)
- [Contributing](#contributing)
- [License](#license)
- **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
## Background
---
DPCalendar does not ship with a Web Services plugin. This plugin fills that gap by registering REST API routes for:
## Requirements
- **Events** — CRUD with date filtering, category scoping, and recurrence support
- **Calendars** — List and manage calendar categories
- **Locations** — List and manage event locations
| Requirement | Version |
|---|---|
| **PHP** | 8.1+ |
| **Joomla** | 5.x or 6.x |
| **DPCalendar** | Required (component must be installed) |
## Install
---
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**
## Installation
## API Endpoints
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
All endpoints require `Authorization: Bearer <token>`.
See the [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/INSTALLATION) wiki page for detailed instructions.
### Events
---
| 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 |
## Authentication
### Calendars
All API requests require a Joomla API token passed via the `Authorization` header:
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/v1/dpcalendar/calendars` | List calendars |
| GET | `/v1/dpcalendar/calendars/{id}` | Get calendar |
```
Authorization: Bearer <your-joomla-api-token>
```
### Locations
Generate a token in **Joomla Admin > Users > Manage > [User] > Joomla API Token tab**.
---
## Endpoints
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 |
---
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/v1/dpcalendar/locations` | List locations |
| GET | `/v1/dpcalendar/locations/{id}` | Get location |
| POST | `/v1/dpcalendar/locations` | Create location |
| PATCH | `/v1/dpcalendar/locations/{id}` | Update location |
| DELETE | `/v1/dpcalendar/locations/{id}` | Delete location |
## License
This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
GPL-3.0-or-later see [LICENSE](LICENSE).
---
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
## Maintainers
[@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 |
+41
View File
@@ -0,0 +1,41 @@
# 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
+4 -4
View File
@@ -3,17 +3,17 @@
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
VERSION: 03.01.00
VERSION: 01.00.00
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Moko Web Services - DPCalendar API</name>
<name>Web Services - DPCalendar API</name>
<author>Moko Consulting</author>
<creationDate>2026-05-10</creationDate>
<creationDate>2026-05-04</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>03.02.00</version>
<version>03.00.00</version>
<description>Exposes DPCalendar events, calendars, and locations via the Joomla Web Services API</description>
<namespace path="src">Moko\Plugin\WebServices\MokoDPCalendarAPI</namespace>
<files>
+4 -109
View File
@@ -6,7 +6,7 @@
*
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
* PATH: /src/src/Extension/MokoDPCalendarAPI.php
* VERSION: 03.01.00
* VERSION: 02.00.00
* BRIEF: Plugin class — registers and handles DPCalendar API routes
*/
@@ -46,11 +46,9 @@ final class MokoDPCalendarAPI extends CMSPlugin
$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));
// Bookings + Tickets
// Bookings
$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/tickets', 'tickets.displayList', [], $defaults));
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/tickets/:id', 'tickets.displayItem', ['id' => '(\d+)'], $defaults));
// ICS export (calendar-level)
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/calendars/:id/ical', 'calendars.ical', ['id' => '(\d+)'], $defaults));
@@ -64,7 +62,7 @@ final class MokoDPCalendarAPI extends CMSPlugin
$controller = $input->get('controller', '', 'string');
$task = $input->get('task', '', 'string');
$validControllers = ['events', 'calendars', 'locations', 'bookings', 'tickets'];
$validControllers = ['events', 'calendars', 'locations', 'bookings'];
if (!in_array($controller, $validControllers, true)) {
return;
}
@@ -72,14 +70,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
$id = $input->getInt('id', 0);
$method = $app->input->getMethod();
// Handle CORS preflight
if ($method === 'OPTIONS') {
$this->setCorsHeaders($app);
$app->setHeader('Content-Length', '0', true);
$app->sendHeaders();
$app->close();
}
try {
$data = match (true) {
// Events
@@ -105,16 +95,11 @@ final class MokoDPCalendarAPI extends CMSPlugin
$controller === 'bookings' && $method === 'GET' && !$id => $this->listBookings($input),
$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),
};
// ICS responses bypass JSON
if (is_string($data)) {
$this->setCorsHeaders($app);
$app->setHeader('Content-Type', 'text/calendar; charset=utf-8', true);
$app->setHeader('Content-Disposition', 'attachment; filename="export.ics"', true);
$app->sendHeaders();
@@ -131,41 +116,16 @@ final class MokoDPCalendarAPI extends CMSPlugin
// ── 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
{
$this->setCorsHeaders($app);
$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();
echo $json;
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$app->close();
}
private function sendError($app, int $code, string $message): void
{
$this->setCorsHeaders($app);
http_response_code($code);
$app->setHeader('Content-Type', 'application/vnd.api+json', true);
$app->sendHeaders();
@@ -272,18 +232,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
->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
$countQuery = clone $query;
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
@@ -794,59 +742,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
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 ──────────────────────────────────────────────
private function eventToIcal(int $id): string
+31 -31
View File
@@ -1,96 +1,96 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 03.02.00
VERSION: 03.00.00
-->
<updates>
<update>
<name>Moko Web Services - DPCalendar API</name>
<description>Moko Web Services - DPCalendar API update</description>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.02.00</version>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>development</tag></tags>
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
</downloads>
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>Moko Web Services - DPCalendar API</name>
<description>Moko Web Services - DPCalendar API update</description>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.02.00</version>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>alpha</tag></tags>
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
</downloads>
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>Moko Web Services - DPCalendar API</name>
<description>Moko Web Services - DPCalendar API update</description>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.02.00</version>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>beta</tag></tags>
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
</downloads>
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>Moko Web Services - DPCalendar API</name>
<description>Moko Web Services - DPCalendar API update</description>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.02.00</version>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>rc</tag></tags>
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
</downloads>
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>Moko Web Services - DPCalendar API</name>
<description>Moko Web Services - DPCalendar API update</description>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.02.00</version>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>stable</tag></tags>
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
</downloads>
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>