🧹 Repository Maintenance #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: 🧹 Repository Maintenance | |
| on: | |
| schedule: | |
| - cron: '0 0 * * 0' # Weekly on Sunday at 00:00 UTC | |
| workflow_dispatch: # Allow manual trigger | |
| permissions: | |
| contents: write | |
| actions: write | |
| pull-requests: write | |
| jobs: | |
| cleanup: | |
| name: 🧹 Weekly Cleanup | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📊 Cleanup summary start | |
| run: | | |
| echo "## 🧹 Weekly Repository Maintenance" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Starting automated cleanup tasks..." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| - name: 🔄 Close stale PRs | |
| uses: actions/stale@v9 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| stale-pr-message: 'This pull request has been inactive for 30 days and will be marked as stale.' | |
| close-pr-message: 'This PR has been closed due to prolonged inactivity.' | |
| days-before-stale: 30 | |
| days-before-close: 7 | |
| stale-pr-label: 'stale' | |
| exempt-pr-labels: 'no-stale,WIP,keep-open' | |
| - name: 🗑️ Delete old workflow runs | |
| uses: Mattraks/delete-workflow-runs@v2 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| repository: ${{ github.repository }} | |
| retain_days: 7 | |
| keep_minimum_runs: 5 | |
| - name: 🗂️ Cleanup caches | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const caches = await github.rest.actions.getActionsCacheList({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const now = new Date(); | |
| let deletedCount = 0; | |
| console.log(`Found ${caches.data.actions_caches.length} caches to process`); | |
| for (const cache of caches.data.actions_caches) { | |
| const createdAt = new Date(cache.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| if (daysOld > 7) { | |
| console.log(`Deleting cache: ${cache.key}, created ${daysOld.toFixed(1)} days ago`); | |
| try { | |
| await github.rest.actions.deleteActionsCacheById({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| cache_id: cache.id | |
| }); | |
| deletedCount++; | |
| console.log(`Successfully deleted cache: ${cache.key}`); | |
| } catch (error) { | |
| console.error(`Failed to delete cache ${cache.key}: ${error}`); | |
| } | |
| } else { | |
| console.log(`Keeping cache: ${cache.key}, only ${daysOld.toFixed(1)} days old`); | |
| } | |
| } | |
| console.log(`Cache cleanup completed. Deleted ${deletedCount} caches.`); | |
| - name: 🏷️ Cleanup old releases | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| console.log('Starting release cleanup...'); | |
| const releases = await github.rest.repos.listReleases({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| const now = new Date(); | |
| const productionReleases = []; | |
| const devReleases = []; | |
| // Categorize releases | |
| for (const release of releases.data) { | |
| if (release.tag_name.includes('dev-') || release.prerelease) { | |
| devReleases.push(release); | |
| } else { | |
| productionReleases.push(release); | |
| } | |
| } | |
| console.log(`Found ${productionReleases.length} production and ${devReleases.length} development releases`); | |
| // Cleanup Dev/Pre-releases (keep 10 newest, delete those older than 30 days) | |
| let devReleasesToDelete = []; | |
| if (devReleases.length > 10) { | |
| devReleases.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); | |
| const oldDevReleases = devReleases.slice(10); | |
| for (const release of oldDevReleases) { | |
| const createdAt = new Date(release.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| if (daysOld > 30) { | |
| devReleasesToDelete.push(release); | |
| } | |
| } | |
| } | |
| // Cleanup Production releases (keep 20 newest, delete those older than 90 days) | |
| let prodReleasesToDelete = []; | |
| if (productionReleases.length > 20) { | |
| productionReleases.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); | |
| const oldProdReleases = productionReleases.slice(20); | |
| for (const release of oldProdReleases) { | |
| const createdAt = new Date(release.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| if (daysOld > 90) { | |
| prodReleasesToDelete.push(release); | |
| } | |
| } | |
| } | |
| // Delete dev releases | |
| for (const release of devReleasesToDelete) { | |
| const createdAt = new Date(release.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| console.log(`Deleting dev release: ${release.tag_name}, created ${daysOld.toFixed(1)} days ago`); | |
| try { | |
| await github.rest.repos.deleteRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id | |
| }); | |
| // Delete corresponding tag | |
| try { | |
| await github.rest.git.deleteRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `tags/${release.tag_name}` | |
| }); | |
| console.log(`Successfully deleted dev release and tag: ${release.tag_name}`); | |
| } catch (tagError) { | |
| console.error(`Failed to delete tag ${release.tag_name}: ${tagError}`); | |
| } | |
| } catch (error) { | |
| console.error(`Failed to delete dev release ${release.tag_name}: ${error}`); | |
| } | |
| } | |
| // Delete production releases | |
| for (const release of prodReleasesToDelete) { | |
| const createdAt = new Date(release.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| console.log(`Deleting production release: ${release.tag_name}, created ${daysOld.toFixed(1)} days ago`); | |
| try { | |
| await github.rest.repos.deleteRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id | |
| }); | |
| // Delete corresponding tag | |
| try { | |
| await github.rest.git.deleteRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `tags/${release.tag_name}` | |
| }); | |
| console.log(`Successfully deleted production release and tag: ${release.tag_name}`); | |
| } catch (tagError) { | |
| console.error(`Failed to delete tag ${release.tag_name}: ${tagError}`); | |
| } | |
| } catch (error) { | |
| console.error(`Failed to delete production release ${release.tag_name}: ${error}`); | |
| } | |
| } | |
| // Summary | |
| console.log(`=== RELEASE CLEANUP SUMMARY ===`); | |
| console.log(`Dev releases deleted: ${devReleasesToDelete.length}`); | |
| console.log(`Production releases deleted: ${prodReleasesToDelete.length}`); | |
| console.log(`Dev releases remaining: ${devReleases.length - devReleasesToDelete.length}`); | |
| console.log(`Production releases remaining: ${productionReleases.length - prodReleasesToDelete.length}`); | |
| - name: 📁 Cleanup old artifacts | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| console.log('Starting artifacts cleanup...'); | |
| const artifacts = await github.rest.actions.listArtifactsForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| const now = new Date(); | |
| let deletedCount = 0; | |
| console.log(`Found ${artifacts.data.artifacts.length} artifacts to process`); | |
| for (const artifact of artifacts.data.artifacts) { | |
| const createdAt = new Date(artifact.created_at); | |
| const daysOld = (now - createdAt) / (1000 * 60 * 60 * 24); | |
| // Delete artifacts older than 30 days | |
| if (daysOld > 30) { | |
| console.log(`Deleting artifact: ${artifact.name}, created ${daysOld.toFixed(1)} days ago`); | |
| try { | |
| await github.rest.actions.deleteArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id | |
| }); | |
| deletedCount++; | |
| console.log(`Successfully deleted artifact: ${artifact.name}`); | |
| } catch (error) { | |
| console.error(`Failed to delete artifact ${artifact.name}: ${error}`); | |
| } | |
| } else { | |
| console.log(`Keeping artifact: ${artifact.name}, only ${daysOld.toFixed(1)} days old`); | |
| } | |
| } | |
| console.log(`Artifacts cleanup completed. Deleted ${deletedCount} artifacts.`); | |
| - name: 📊 Cleanup summary | |
| run: | | |
| echo "### ✅ Cleanup Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Task | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stale PRs | ✅ Processed |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Workflow Runs | ✅ Cleaned |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Caches | ✅ Cleaned |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Releases | ✅ Cleaned |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Artifacts | ✅ Cleaned |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Next maintenance:** $(date -d '+7 days' '+%Y-%m-%d')" >> $GITHUB_STEP_SUMMARY |