Skip to content

🧹 Repository Maintenance #27

🧹 Repository Maintenance

🧹 Repository Maintenance #27

Workflow file for this run

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