Files
awesome-docker/tests/health_check.mjs
Julien Bisconti 5b46451014 Modernization (thanks to AI) (#1187)
* -  Removed 3 broken links (labex.io, hashnode.com entries)
-  Fixed rust-lang.org redirect issue
-  Added problematic domains to exclusion list (YouTube playlists, aquasec, cloudsmith)
-  Updated all npm dependencies to latest versions

-  **health_check.mjs** - Comprehensive repository health checker
  - Detects archived repositories
  - Identifies stale projects (2+ years inactive)
  - Flags inactive projects (1-2 years)
  - Generates detailed health reports
  - Run with: `npm run health-check`

-  **test_all.mjs** - Now detects archived repositories
  - Added `isArchived` field to GraphQL query
  - Warns about archived repos that should be marked `💀`
  - Non-blocking warnings (doesn't fail builds)

- Runs every Monday at 9 AM UTC
- Checks all 731+ GitHub repositories for health
- Auto-creates/updates GitHub issue with findings
- Labels: `health-report`, `maintenance`
- Manual trigger available

- Runs every Saturday at 2 AM UTC
- Tests all external links
- Auto-creates issue when links break
- Auto-closes issue when all links fixed
- Labels: `broken-links`, `bug`

- Already checks for duplicates
- Now also checks for archived repos
- Validates link format and availability

-  **MAINTENANCE.md** - Complete guide for maintainers
  - Monthly, quarterly, and annual tasks
  - Emergency procedures
  - Quality standards
  - Metrics to track

-  **AGENTS.md** - Updated with new commands
  - Added health-check command
  - Noted GITHUB_TOKEN requirements
  - Added alphabetical sorting guideline

- **Total Links**: 883 (731 GitHub repos + 152 external)
- **Working Links**: >99% (after fixes)
- **Abandoned Projects**: 15 marked with `💀`
- **Automated Checks**: 3 workflows running

- **Automatic detection** of abandoned/archived projects
- **Weekly monitoring** ensures issues are caught early
- **Proactive alerts** via GitHub issues

- No more manual link checking (automated weekly)
- Archived repos detected automatically
- Contributors get instant PR feedback

- Health metrics tracked over time
- Clear standards documented
- Easy onboarding for new maintainers

- Monday: Health report generated and posted
- Saturday: Link validation runs

- Review health report issue
- Mark any newly archived projects with `💀`

- Run full health check: `npm run health-check`
- Review inactive projects (1-2 years)
- Consider removing very old abandoned projects

- Deep cleanup of `💀` projects
- Update documentation
- Review categories and organization

1. **Auto-PR for Archived Repos**: Bot could auto-create PRs to mark archived repos
2. **Contribution Stats**: Track and display top contributors
3. **Category Health**: Per-category health metrics
4. **Dependency Updates**: Dependabot for npm packages
5. **Star Trending**: Track which projects are gaining popularity

- `tests/health_check.mjs` - Health checker script
- `.github/workflows/health_report.yml` - Weekly health workflow
- `.github/workflows/broken_links.yml` - Link validation workflow
- `.github/MAINTENANCE.md` - Maintainer guide
- `AGENTS.md` - AI agent guidelines

- `README.md` - Removed 3 broken links, fixed 1 redirect
- `tests/test_all.mjs` - Added archive detection
- `tests/exclude_in_test.json` - Added problematic domains
- `package.json` - Added health-check script
- `package-lock.json` - Updated dependencies

Before: Manual maintenance, broken links accumulate, outdated projects linger
After: **Automated health monitoring, proactive issue detection, systematic maintenance**

The list is now **self-maintaining** with minimal human oversight required.

---

*Generated: 2025-10-01*

* update github actions

* remove dead links

* set timeout

* Add badges
2025-10-02 15:03:59 +02:00

207 lines
6.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'fs-extra';
import fetch from 'node-fetch';
import helper from './common.mjs';
const README = 'README.md';
const GITHUB_GQL_API = 'https://api.github.com/graphql';
const TOKEN = process.env.GITHUB_TOKEN || '';
if (!TOKEN) {
console.error('GITHUB_TOKEN environment variable is required');
process.exit(1);
}
const Authorization = `token ${TOKEN}`;
const LOG = {
info: (...args) => console.log(' ', ...args),
warn: (...args) => console.warn('⚠️ ', ...args),
error: (...args) => console.error('❌', ...args),
};
// Extract GitHub repos from links
const extract_repos = (arr) =>
arr
.map((e) => e.substr('https://github.com/'.length).split('/'))
.filter((r) => r.length === 2 && r[1] !== '');
// Generate GraphQL query to check repo health
const generate_health_query = (repos) => {
const repoQueries = repos.map(([owner, name]) => {
const safeName = `repo_${owner.replace(/(-|\.)/g, '_')}_${name.replace(/(-|\.)/g, '_')}`;
return `${safeName}: repository(owner: "${owner}", name:"${name}"){
nameWithOwner
isArchived
pushedAt
createdAt
stargazerCount
forkCount
isDisabled
isFork
isLocked
isPrivate
}`;
}).join('\n');
return `query REPO_HEALTH { ${repoQueries} }`;
};
// Batch repos into smaller chunks for GraphQL
function* batchRepos(repos, size = 50) {
for (let i = 0; i < repos.length; i += size) {
yield repos.slice(i, i + size);
}
}
async function checkRepoHealth(repos) {
const results = {
archived: [],
stale: [], // No commits in 2+ years
inactive: [], // No commits in 1-2 years
healthy: [],
disabled: [],
total: repos.length,
};
const twoYearsAgo = new Date();
twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2);
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
LOG.info(`Checking health of ${repos.length} repositories...`);
for (const batch of batchRepos(repos)) {
const query = generate_health_query(batch);
const options = {
method: 'POST',
headers: {
Authorization,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
};
try {
const response = await fetch(GITHUB_GQL_API, options);
const data = await response.json();
if (data.errors) {
LOG.error('GraphQL errors:', data.errors);
continue;
}
for (const [key, repo] of Object.entries(data.data)) {
if (!repo) continue;
const pushedAt = new Date(repo.pushedAt);
const repoInfo = {
name: repo.nameWithOwner,
pushedAt: repo.pushedAt,
stars: repo.stargazerCount,
url: `https://github.com/${repo.nameWithOwner}`,
};
if (repo.isArchived) {
results.archived.push(repoInfo);
} else if (repo.isDisabled) {
results.disabled.push(repoInfo);
} else if (pushedAt < twoYearsAgo) {
results.stale.push(repoInfo);
} else if (pushedAt < oneYearAgo) {
results.inactive.push(repoInfo);
} else {
results.healthy.push(repoInfo);
}
}
} catch (error) {
LOG.error('Batch fetch error:', error.message);
}
// Rate limiting - wait a bit between batches
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
function generateReport(results) {
const report = [];
report.push('# 🏥 Awesome Docker - Health Check Report\n');
report.push(`**Generated:** ${new Date().toISOString()}\n`);
report.push(`**Total Repositories:** ${results.total}\n`);
report.push('\n## 📊 Summary\n');
report.push(`- ✅ Healthy (updated in last year): ${results.healthy.length}`);
report.push(`- ⚠️ Inactive (1-2 years): ${results.inactive.length}`);
report.push(`- 🪦 Stale (2+ years): ${results.stale.length}`);
report.push(`- 📦 Archived: ${results.archived.length}`);
report.push(`- 🚫 Disabled: ${results.disabled.length}\n`);
if (results.archived.length > 0) {
report.push('\n## 📦 Archived Repositories (Should mark as :skull:)\n');
results.archived.forEach(repo => {
report.push(`- [${repo.name}](${repo.url}) - ⭐ ${repo.stars} - Last push: ${repo.pushedAt}`);
});
}
if (results.stale.length > 0) {
report.push('\n## 🪦 Stale Repositories (No activity in 2+ years)\n');
results.stale.slice(0, 50).forEach(repo => {
report.push(`- [${repo.name}](${repo.url}) - ⭐ ${repo.stars} - Last push: ${repo.pushedAt}`);
});
if (results.stale.length > 50) {
report.push(`\n... and ${results.stale.length - 50} more`);
}
}
if (results.inactive.length > 0) {
report.push('\n## ⚠️ Inactive Repositories (No activity in 1-2 years)\n');
report.push('_These may still be stable/complete projects - review individually_\n');
results.inactive.slice(0, 30).forEach(repo => {
report.push(`- [${repo.name}](${repo.url}) - ⭐ ${repo.stars} - Last push: ${repo.pushedAt}`);
});
if (results.inactive.length > 30) {
report.push(`\n... and ${results.inactive.length - 30} more`);
}
}
return report.join('\n');
}
async function main() {
const markdown = await fs.readFile(README, 'utf8');
let links = helper.extract_all_links(markdown);
const github_links = links.filter(link =>
link.startsWith('https://github.com') &&
!helper.exclude_from_list(link) &&
!link.includes('/issues') &&
!link.includes('/pull') &&
!link.includes('/wiki') &&
!link.includes('#')
);
const repos = extract_repos(github_links);
const results = await checkRepoHealth(repos);
const report = generateReport(results);
// Save report
await fs.writeFile('HEALTH_REPORT.md', report);
LOG.info('Health report saved to HEALTH_REPORT.md');
// Also print summary to console
console.log('\n' + report);
// Exit with error if there are actionable items
if (results.archived.length > 0 || results.stale.length > 10) {
LOG.warn(`Found ${results.archived.length} archived and ${results.stale.length} stale repos`);
process.exit(1);
}
}
console.log('Starting health check...');
main();