chore: Sync MokoStandards 04.02.29 #104

Closed
jmiller-moko wants to merge 38 commits from chore/sync-mokostandards-v04.02.29 into main
Showing only changes of commit 96a948c814 - Show all commits

View File

@@ -9,36 +9,58 @@
# INGROUP: MokoStandards.Maintenance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/repository-cleanup.yml
# VERSION: 04.01.00
# BRIEF: One-time repository cleanup — reset labels, strip issue template headers, delete old branches
# VERSION: 04.02.30
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
# Run manually via workflow_dispatch. Safe to re-run — all operations are idempotent.
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
name: Repository Cleanup
on:
schedule:
- cron: '0 6 1,15 * *'
workflow_dispatch:
inputs:
reset_labels:
description: 'Delete ALL existing labels and recreate the standard 54-label set'
description: 'Delete ALL existing labels and recreate the standard set'
type: boolean
default: false
clean_branches:
description: 'Delete old chore/sync-mokostandards-* branches'
type: boolean
default: true
clean_branches:
description: 'Delete old chore/sync-mokostandards-* branches (keeps current versioned branch only)'
clean_workflows:
description: 'Delete orphaned workflow runs (cancelled, stale)'
type: boolean
default: true
clean_logs:
description: 'Delete workflow run logs older than 30 days'
type: boolean
default: true
fix_templates:
description: 'Strip copyright comment blocks from issue templates'
type: boolean
default: true
rebuild_indexes:
description: 'Rebuild docs/ index files'
type: boolean
default: true
delete_closed_issues:
description: 'Delete issues that have been closed for more than 30 days'
type: boolean
default: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: write
issues: write
actions: write
jobs:
cleanup:
name: Repository Cleanup
name: Repository Maintenance
runs-on: ubuntu-latest
steps:
@@ -46,12 +68,18 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GH_TOKEN || github.token }}
fetch-depth: 0
- name: Check actor permission
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
ACTOR="${{ github.actor }}"
# Schedule triggers use github-actions[bot]
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "✅ Scheduled run — authorized"
exit 0
fi
AUTHORIZED_USERS="jmiller-moko github-actions[bot]"
for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then
@@ -66,9 +94,90 @@ jobs:
*) echo "❌ Admin or maintain required"; exit 1 ;;
esac
# ── Determine which tasks to run ─────────────────────────────────────
# On schedule: run all tasks with safe defaults (labels NOT reset)
# On dispatch: use input toggles
- name: Set task flags
id: tasks
run: |
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "reset_labels=false" >> $GITHUB_OUTPUT
echo "clean_branches=true" >> $GITHUB_OUTPUT
echo "clean_workflows=true" >> $GITHUB_OUTPUT
echo "clean_logs=true" >> $GITHUB_OUTPUT
echo "fix_templates=true" >> $GITHUB_OUTPUT
echo "rebuild_indexes=true" >> $GITHUB_OUTPUT
echo "delete_closed_issues=false" >> $GITHUB_OUTPUT
else
echo "reset_labels=${{ inputs.reset_labels }}" >> $GITHUB_OUTPUT
echo "clean_branches=${{ inputs.clean_branches }}" >> $GITHUB_OUTPUT
echo "clean_workflows=${{ inputs.clean_workflows }}" >> $GITHUB_OUTPUT
echo "clean_logs=${{ inputs.clean_logs }}" >> $GITHUB_OUTPUT
echo "fix_templates=${{ inputs.fix_templates }}" >> $GITHUB_OUTPUT
echo "rebuild_indexes=${{ inputs.rebuild_indexes }}" >> $GITHUB_OUTPUT
echo "delete_closed_issues=${{ inputs.delete_closed_issues }}" >> $GITHUB_OUTPUT
fi
# ── DELETE RETIRED WORKFLOWS (always runs) ────────────────────────────
- name: Delete retired workflow files
run: |
echo "## 🗑️ Retired Workflow Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
RETIRED=(
".github/workflows/build.yml"
".github/workflows/code-quality.yml"
".github/workflows/release-cycle.yml"
".github/workflows/release-pipeline.yml"
".github/workflows/branch-cleanup.yml"
".github/workflows/auto-update-changelog.yml"
".github/workflows/enterprise-issue-manager.yml"
".github/workflows/flush-actions-cache.yml"
".github/workflows/mokostandards-script-runner.yml"
".github/workflows/unified-ci.yml"
".github/workflows/unified-platform-testing.yml"
".github/workflows/reusable-build.yml"
".github/workflows/reusable-ci-validation.yml"
".github/workflows/reusable-deploy.yml"
".github/workflows/reusable-php-quality.yml"
".github/workflows/reusable-platform-testing.yml"
".github/workflows/reusable-project-detector.yml"
".github/workflows/reusable-release.yml"
".github/workflows/reusable-script-executor.yml"
".github/workflows/rebuild-docs-indexes.yml"
".github/workflows/setup-project-v2.yml"
".github/workflows/sync-docs-to-project.yml"
".github/workflows/release.yml"
".github/workflows/sync-changelogs.yml"
".github/workflows/version_branch.yml"
"update.json"
".github/workflows/auto-version-branch.yml"
)
DELETED=0
for wf in "${RETIRED[@]}"; do
if [ -f "$wf" ]; then
git rm "$wf" 2>/dev/null || rm -f "$wf"
echo " Deleted: \`$(basename $wf)\`" >> $GITHUB_STEP_SUMMARY
DELETED=$((DELETED+1))
fi
done
if [ "$DELETED" -gt 0 ]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore: delete ${DELETED} retired workflow file(s) [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ ${DELETED} retired workflow(s) deleted" >> $GITHUB_STEP_SUMMARY
else
echo "✅ No retired workflows found" >> $GITHUB_STEP_SUMMARY
fi
# ── LABEL RESET ──────────────────────────────────────────────────────
- name: Reset labels to standard set
if: inputs.reset_labels == true
if: steps.tasks.outputs.reset_labels == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
@@ -76,23 +185,16 @@ jobs:
echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Delete all existing labels
echo "Deleting existing labels..."
DELETED=0
gh api "repos/${REPO}/labels?per_page=100" --paginate --jq '.[].name' | while read -r label; do
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))")
gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null && DELETED=$((DELETED+1)) || true
gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null || true
done
echo "Deleted existing labels" >> $GITHUB_STEP_SUMMARY
# Create the standard 54-label set
echo "Creating standard labels..."
CREATED=0
while IFS='|' read -r name color description; do
[ -z "$name" ] && continue
gh api "repos/${REPO}/labels" \
-f name="$name" -f color="$color" -f description="$description" \
--silent 2>/dev/null && CREATED=$((CREATED+1)) || true
--silent 2>/dev/null || true
done << 'LABELS'
joomla|7F52FF|Joomla extension or component
dolibarr|FF6B6B|Dolibarr module or extension
@@ -125,6 +227,7 @@ jobs:
type: enhancement|84B6EB|Enhancement to existing feature
type: refactor|F9D0C4|Code refactoring
type: chore|FEF2C0|Maintenance tasks
type: version|0E8A16|Version-related change
status: pending|FBCA04|Pending action or decision
status: in-progress|0E8A16|Currently being worked on
status: blocked|B60205|Blocked by another issue or dependency
@@ -149,45 +252,85 @@ jobs:
version-drift|FFA500|Version mismatch detected
deploy-failure|CC0000|Automated deploy failure tracking
template-validation-failure|D73A4A|Template workflow validation failure
version|0E8A16|Version bump or release
LABELS
echo "✅ Standard labels created" >> $GITHUB_STEP_SUMMARY
# ── BRANCH CLEANUP ───────────────────────────────────────────────────
- name: Delete old sync branches
if: inputs.clean_branches == true
if: steps.tasks.outputs.clean_branches == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
CURRENT="chore/sync-mokostandards-v04.01.00"
CURRENT="chore/sync-mokostandards-v04.02.29"
echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DELETED=0
FOUND=false
gh api "repos/${REPO}/branches?per_page=100" --jq '.[].name' | \
grep "^chore/sync-mokostandards" | \
grep -v "^${CURRENT}$" | while read -r branch; do
# Close any open PRs on this branch
gh pr list --repo "$REPO" --head "$branch" --state open --json number --jq '.[].number' 2>/dev/null | while read -r pr; do
gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true
echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY
done
# Delete the branch
gh api -X DELETE "repos/${REPO}/git/refs/heads/${branch}" --silent 2>/dev/null || true
echo " Deleted: \`${branch}\`" >> $GITHUB_STEP_SUMMARY
FOUND=true
done
if [ "$FOUND" != "true" ]; then
echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY
fi
# ── WORKFLOW RUN CLEANUP ─────────────────────────────────────────────
- name: Clean up workflow runs
if: steps.tasks.outputs.clean_workflows == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
echo "## 🔄 Workflow Run Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DELETED=0
# Delete cancelled and stale workflow runs
for status in cancelled stale; do
gh api "repos/${REPO}/actions/runs?status=${status}&per_page=100" \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}" --silent 2>/dev/null || true
DELETED=$((DELETED+1))
done
done
echo "✅ Cleaned cancelled/stale workflow runs" >> $GITHUB_STEP_SUMMARY
# ── LOG CLEANUP ──────────────────────────────────────────────────────
- name: Delete old workflow run logs
if: steps.tasks.outputs.clean_logs == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
echo "## 📋 Log Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deleting logs older than: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
DELETED=0
gh api "repos/${REPO}/actions/runs?created=<${CUTOFF}&per_page=100" \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}/logs" --silent 2>/dev/null || true
DELETED=$((DELETED+1))
done
if [ "$DELETED" -eq 0 ] 2>/dev/null; then
echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Cleanup complete" >> $GITHUB_STEP_SUMMARY
fi
echo "✅ Cleaned old workflow run logs" >> $GITHUB_STEP_SUMMARY
# ── ISSUE TEMPLATE FIX ──────────────────────────────────────────────
- name: Strip copyright headers from issue templates
if: inputs.fix_templates == true
if: steps.tasks.outputs.fix_templates == 'true'
run: |
echo "## 📋 Issue Template Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
@@ -214,26 +357,158 @@ jobs:
echo "✅ No templates need cleaning" >> $GITHUB_STEP_SUMMARY
fi
# ── SELF-DELETE ─────────────────────────────────────────────────────
- name: Delete this workflow (one-time use)
if: success()
# ── REBUILD DOC INDEXES ─────────────────────────────────────────────
- name: Rebuild docs/ index files
if: steps.tasks.outputs.rebuild_indexes == 'true'
run: |
echo "## 📚 Documentation Index Rebuild" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -d "docs" ]; then
echo "⏭️ No docs/ directory — skipping" >> $GITHUB_STEP_SUMMARY
exit 0
fi
UPDATED=0
# Generate index.md for each docs/ subdirectory
find docs -type d | while read -r dir; do
INDEX="${dir}/index.md"
FILES=$(find "$dir" -maxdepth 1 -name "*.md" ! -name "index.md" -printf "- [%f](./%f)\n" 2>/dev/null | sort)
if [ -z "$FILES" ]; then
continue
fi
cat > "$INDEX" << INDEXEOF
# $(basename "$dir")
## Documents
${FILES}
---
*Auto-generated by repository-cleanup workflow*
INDEXEOF
# Dedent
sed -i 's/^ //' "$INDEX"
UPDATED=$((UPDATED+1))
done
if [ "$UPDATED" -gt 0 ]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add docs/
if ! git diff --cached --quiet; then
git commit -m "docs: rebuild documentation indexes [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ ${UPDATED} index file(s) rebuilt and committed" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All indexes already up to date" >> $GITHUB_STEP_SUMMARY
fi
else
echo "✅ No indexes to rebuild" >> $GITHUB_STEP_SUMMARY
fi
# ── VERSION DRIFT DETECTION ──────────────────────────────────────────
- name: Check for version drift
run: |
echo "## 📦 Version Drift Check" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -f "README.md" ]; then
echo "⏭️ No README.md — skipping" >> $GITHUB_STEP_SUMMARY
exit 0
fi
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md 2>/dev/null | head -1)
if [ -z "$README_VERSION" ]; then
echo "⚠️ No VERSION found in README.md FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY
exit 0
fi
echo "**README version:** \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DRIFT=0
CHECKED=0
# Check all files with FILE INFORMATION blocks
while IFS= read -r -d '' file; do
FILE_VERSION=$(grep -oP '^\s*\*?\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' "$file" 2>/dev/null | head -1)
[ -z "$FILE_VERSION" ] && continue
CHECKED=$((CHECKED+1))
if [ "$FILE_VERSION" != "$README_VERSION" ]; then
echo " ⚠️ \`${file}\`: \`${FILE_VERSION}\` (expected \`${README_VERSION}\`)" >> $GITHUB_STEP_SUMMARY
DRIFT=$((DRIFT+1))
fi
done < <(find . -maxdepth 4 -type f \( -name "*.php" -o -name "*.md" -o -name "*.yml" \) ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -print0 2>/dev/null)
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$DRIFT" -gt 0 ]; then
echo "⚠️ **${DRIFT}** file(s) out of ${CHECKED} have version drift" >> $GITHUB_STEP_SUMMARY
echo "Run \`sync-version-on-merge\` workflow or update manually" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All ${CHECKED} file(s) match README version \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# ── PROTECT CUSTOM WORKFLOWS ────────────────────────────────────────
- name: Ensure custom workflow directory exists
run: |
echo "## 🔧 Custom Workflows" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -d ".github/workflows/custom" ]; then
mkdir -p .github/workflows/custom
cat > .github/workflows/custom/README.md << 'CWEOF'
# Custom Workflows
Place repo-specific workflows here. Files in this directory are:
- **Never overwritten** by MokoStandards bulk sync
- **Never deleted** by the repository-cleanup workflow
- Safe for custom CI, notifications, or repo-specific automation
Synced workflows live in `.github/workflows/` (parent directory).
CWEOF
sed -i 's/^ //' .github/workflows/custom/README.md
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .github/workflows/custom/
if ! git diff --cached --quiet; then
git commit -m "chore: create .github/workflows/custom/ for repo-specific workflows [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ Created \`.github/workflows/custom/\` directory" >> $GITHUB_STEP_SUMMARY
fi
else
CUSTOM_COUNT=$(find .github/workflows/custom -name "*.yml" -o -name "*.yaml" 2>/dev/null | wc -l)
echo "✅ Custom workflow directory exists (${CUSTOM_COUNT} workflow(s))" >> $GITHUB_STEP_SUMMARY
fi
# ── DELETE CLOSED ISSUES ──────────────────────────────────────────────
- name: Delete old closed issues
if: steps.tasks.outputs.delete_closed_issues == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
echo "## 🗑️ Self-Cleanup" >> $GITHUB_STEP_SUMMARY
REPO="${{ github.repository }}"
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
echo "## 🗑️ Closed Issue Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deleting issues closed before: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
WORKFLOW_FILE=".github/workflows/repository-cleanup.yml"
if [ -f "$WORKFLOW_FILE" ]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git rm "$WORKFLOW_FILE"
git commit -m "chore: remove repository-cleanup.yml after successful run [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ Workflow file deleted — it will not appear in future syncs" >> $GITHUB_STEP_SUMMARY
DELETED=0
gh api "repos/${REPO}/issues?state=closed&since=1970-01-01T00:00:00Z&per_page=100&sort=updated&direction=asc" \
--jq ".[] | select(.closed_at < \"${CUTOFF}\") | .number" 2>/dev/null | while read -r num; do
# Lock and close with "not_planned" to mark as cleaned up
gh api "repos/${REPO}/issues/${num}/lock" -X PUT -f lock_reason="resolved" --silent 2>/dev/null || true
echo " Locked issue #${num}" >> $GITHUB_STEP_SUMMARY
DELETED=$((DELETED+1))
done
if [ "$DELETED" -eq 0 ] 2>/dev/null; then
echo "✅ No old closed issues found" >> $GITHUB_STEP_SUMMARY
else
echo " Workflow file already removed" >> $GITHUB_STEP_SUMMARY
echo "✅ Locked ${DELETED} old closed issue(s)" >> $GITHUB_STEP_SUMMARY
fi
- name: Summary
@@ -241,4 +516,4 @@ jobs:
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "*Run by @${{ github.actor }} via workflow_dispatch*" >> $GITHUB_STEP_SUMMARY
echo "*Run by @${{ github.actor }} — trigger: ${{ github.event_name }}*" >> $GITHUB_STEP_SUMMARY