chore: cascade main → dev (d3dc424) [skip ci] #9

Merged
jmiller merged 5 commits from main into dev 2026-05-09 18:20:32 +00:00
12 changed files with 694 additions and 124 deletions
+59
View File
@@ -0,0 +1,59 @@
{
"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"
]
}
@@ -3,9 +3,9 @@
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
See: docs/standards/moko-platform-file-spec.md
-->
<mokostandards xmlns="https://standards.mokoconsulting.tech/mokostandards/1.0" schema-version="1.0">
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity>
<name>MokoDPCalendarAPI</name>
<org>MokoConsulting</org>
@@ -35,4 +35,4 @@
<runner>make</runner>
</script>
</scripts>
</mokostandards>
</moko-platform>
+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
+73
View File
@@ -375,3 +375,76 @@ 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
@@ -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
+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
+24
View File
@@ -12,6 +12,30 @@ 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
-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
+26
View File
@@ -0,0 +1,26 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly",
":disableDependencyDashboard"
],
"labels": ["dependencies"],
"automerge": false,
"platformAutomerge": false,
"rangeStrategy": "bump",
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
},
{
"matchManagers": ["composer"],
"enabled": true
},
{
"matchManagers": ["npm"],
"enabled": true
}
]
}
+2 -2
View File
@@ -3,7 +3,7 @@
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
VERSION: 03.01.00
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - DPCalendar API</name>
@@ -13,7 +13,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>03.00.01</version>
<version>03.01.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>
+1 -1
View File
@@ -6,7 +6,7 @@
*
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
* PATH: /src/src/Extension/MokoDPCalendarAPI.php
* VERSION: 02.00.00
* VERSION: 03.01.00
* BRIEF: Plugin class — registers and handles DPCalendar API routes
*/
+4 -77
View File
@@ -1,7 +1,7 @@
<?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.00.00
VERSION: 03.01.00
-->
<updates>
@@ -10,87 +10,14 @@
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.00.01</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>development</tag></tags>
<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/development/mokodpcalendarapi-03.00.01-dev.zip</downloadurl>
</downloads>
<sha256>48aaa0c5935fb5c04535fd84375407e82690325a543c22c23103387b60f8a8c7</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>alpha</tag></tags>
<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.00.00.zip</downloadurl>
</downloads>
<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>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>beta</tag></tags>
<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.00.00.zip</downloadurl>
</downloads>
<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>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.00.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>rc</tag></tags>
<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.00.00.zip</downloadurl>
</downloads>
<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>Web Services - DPCalendar API</name>
<description>Web Services - DPCalendar API update</description>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<version>03.00.00</version>
<version>03.01.00</version>
<client>site</client>
<folder>webservices</folder>
<tags><tag>stable</tag></tags>
<infourl title="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/v03.01.00</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/v03.01.00/mokodpcalendarapi-03.01.00.zip</downloadurl>
</downloads>
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>