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
This commit is contained in:
Julien Bisconti
2025-10-02 15:03:59 +02:00
committed by GitHub
parent cb2b7788f2
commit 5b46451014
15 changed files with 824 additions and 710 deletions

View File

@@ -9,6 +9,8 @@ const LINKS_OPTIONS = {
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
},
timeout: 60000, // 1m
signal: AbortSignal.timeout(60000),
};
const LOG = {

View File

@@ -10,5 +10,8 @@
"https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg",
"https://www.se-radio.net/2017/05/se-radio-episode-290-diogo-monica-on-docker-security",
"https://www.reddit.com/r/docker/",
"https://www.udacity.com/course/scalable-microservices-with-kubernetes--ud615"
"https://www.udacity.com/course/scalable-microservices-with-kubernetes--ud615",
"https://www.youtube.com/playlist",
"https://www.aquasec.com",
"https://cloudsmith.com"
]

206
tests/health_check.mjs Normal file
View File

@@ -0,0 +1,206 @@
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();

View File

@@ -63,4 +63,7 @@ async function main() {
}
console.log('starting...');
main();
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -39,7 +39,7 @@ const generate_GQL_query = (arr) =>
`repo_${owner.replace(/(-|\.)/g, '_')}_${name.replace(
/(-|\.)/g,
'_',
)}: repository(owner: "${owner}", name:"${name}"){ nameWithOwner } `,
)}: repository(owner: "${owner}", name:"${name}"){ nameWithOwner isArchived } `,
)
.join('')} }`;
@@ -95,6 +95,22 @@ async function main() {
has_error.github_repos = gql_response.errors;
}
// Check for archived repositories
console.log('checking for archived repositories...');
const archived_repos = [];
if (gql_response.data) {
for (const [key, repo] of Object.entries(gql_response.data)) {
if (repo && repo.isArchived) {
archived_repos.push(repo.nameWithOwner);
}
}
}
if (archived_repos.length > 0) {
console.warn(`⚠️ Found ${archived_repos.length} archived repositories that should be marked with :skull:`);
console.warn('Archived repos:', archived_repos);
// Don't fail the build, just warn
}
console.log({
TEST_PASSED: has_error.show,
GITHUB_REPOSITORY: github_links.length,