mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 18:35:14 +00:00
- Improve contributor-report.mjs based on Copilot comments and sonar - Use fileURLToPath for Windows-safe main checks in contributor scripts - Fix README badge URLs by removing duplicate style parameters - Update graceful-shutdown.mjs to throw on process.exit failure for better error visibility Generated-by: GitHub Copilot <copilot@github.com> Signed-off-by: Ashley Childress <6563688+anchildress1@users.noreply.github.com>
308 lines
9.8 KiB
JavaScript
308 lines
9.8 KiB
JavaScript
/**
|
||
* One-time contributor detection and addition script.
|
||
* Discovers missing contributors, determines their contribution types from repo history,
|
||
* and updates .all-contributorsrc via the all-contributors CLI.
|
||
*
|
||
* Usage: node add-missing-contributors.mjs
|
||
*/
|
||
import { execSync } from 'node:child_process';
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import {
|
||
getContributionTypes,
|
||
getMissingContributors,
|
||
fetchContributorMergedPrs
|
||
} from './contributor-report.mjs';
|
||
import { setupGracefulShutdown } from './utils/graceful-shutdown.mjs';
|
||
|
||
const DEFAULT_CMD_TIMEOUT = 30_000; // 30 seconds
|
||
|
||
setupGracefulShutdown('add-missing-contributors');
|
||
|
||
/**
|
||
* Get all files touched by a contributor from their merged PRs.
|
||
* @param {string} username
|
||
* @returns {string[]}
|
||
*/
|
||
const getContributorFiles = (username) => {
|
||
try {
|
||
console.log(`📁 Getting files for contributor: ${username}`);
|
||
|
||
const prs = fetchContributorMergedPrs(username, { includeAllFiles: true });
|
||
|
||
if (prs.length === 0) {
|
||
console.log(`📭 No merged PRs found for ${username}`);
|
||
return [];
|
||
}
|
||
|
||
const files = new Set();
|
||
for (const pr of prs) {
|
||
for (const file of pr.files || []) {
|
||
if (file?.path) {
|
||
files.add(file.path);
|
||
}
|
||
}
|
||
}
|
||
|
||
const fileList = Array.from(files);
|
||
console.log(`📄 Found ${fileList.length} unique files for ${username}: ${fileList.slice(0, 3).join(', ')}${fileList.length > 3 ? '...' : ''}`);
|
||
return fileList;
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error getting files for ${username}:`, error.message);
|
||
return [];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Determine contribution types from a contributor's files.
|
||
* @param {string} username
|
||
* @returns {string}
|
||
*/
|
||
const analyzeContributor = (username) => {
|
||
try {
|
||
console.log(`🔍 Analyzing contribution types for: ${username}`);
|
||
const files = getContributorFiles(username);
|
||
|
||
if (files.length === 0) {
|
||
console.log(`💡 No files found for ${username}, using 'code' fallback`);
|
||
return 'code';
|
||
}
|
||
|
||
const contributionTypes = getContributionTypes(files);
|
||
|
||
if (!contributionTypes || contributionTypes.trim() === '') {
|
||
console.log(`💡 No matching types found for ${username}, using 'code' fallback`);
|
||
return 'code';
|
||
}
|
||
|
||
console.log(`✅ Determined types for ${username}: ${contributionTypes}`);
|
||
return contributionTypes;
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error analyzing files for ${username}:`, error.message);
|
||
return 'code';
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Add a username to the ignore list in .all-contributorsrc.
|
||
* @param {string} username
|
||
* @returns {boolean}
|
||
*/
|
||
const addToIgnoreList = (username) => {
|
||
try {
|
||
const configPath = path.join(process.cwd(), '.all-contributorsrc');
|
||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||
|
||
const ignoreList = config.ignoreList || config.ignore || [];
|
||
if (!ignoreList.includes(username)) {
|
||
ignoreList.push(username);
|
||
config.ignoreList = ignoreList;
|
||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||
console.warn(`⚠️ Added ${username} to ignore list (user not found on GitHub)`);
|
||
return true;
|
||
}
|
||
return false;
|
||
} catch (error) {
|
||
console.error(`❌ Failed to add ${username} to ignore list:`, error.message);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Run the all-contributors CLI to add a contributor to the project.
|
||
* @param {string} username
|
||
* @param {string} types
|
||
* @returns {boolean}
|
||
*/
|
||
const addContributor = (username, types) => {
|
||
try {
|
||
console.log(`➕ Adding contributor: ${username} with types: ${types}`);
|
||
|
||
const command = `npx all-contributors add ${username} ${types}`;
|
||
|
||
execSync(command, {
|
||
encoding: 'utf8',
|
||
stdio: ['pipe', 'pipe', 'pipe'],
|
||
timeout: DEFAULT_CMD_TIMEOUT
|
||
});
|
||
|
||
return true;
|
||
|
||
} catch (error) {
|
||
// System-level errors that should propagate up
|
||
if (error.message.includes('rate limit') || error.message.includes('403')) {
|
||
console.error(`⏱️ Rate limit encountered while adding ${username}.`);
|
||
throw error;
|
||
}
|
||
if (error.message.includes('network') || error.message.includes('timeout')) {
|
||
console.error(`🌐 Network error while adding ${username}.`);
|
||
throw error;
|
||
}
|
||
|
||
// User-specific errors that can be skipped
|
||
if (error.message.includes('404') || error.message.includes('not found')) {
|
||
addToIgnoreList(username);
|
||
console.error(`❌ User ${username} not found, added to ignore list`);
|
||
return false;
|
||
}
|
||
|
||
// Unknown error - log and skip
|
||
console.error(`❌ Failed to add contributor ${username}:`, error.message);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Process a single missing contributor: detect types and add via all-contributors CLI.
|
||
* @param {string} username
|
||
* @returns {{added:number, failed:number}}
|
||
*/
|
||
const processContributor = async (username) => {
|
||
let added = 0;
|
||
let failed = 0;
|
||
|
||
try {
|
||
console.log(`📊 Step 2: Analyzing contribution types for ${username}...`);
|
||
const contributionTypes = analyzeContributor(username);
|
||
|
||
console.log(`➕ Step 3: Adding ${username} with types: ${contributionTypes}...`);
|
||
|
||
const success = addContributor(username, contributionTypes);
|
||
if (success) {
|
||
added++;
|
||
console.log(`✅ Successfully processed ${username}`);
|
||
} else {
|
||
failed++;
|
||
console.log(`❌ Failed to process ${username}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
failed++;
|
||
console.error(`💥 Error processing ${username}:`, error.message);
|
||
}
|
||
|
||
return { added, failed };
|
||
};
|
||
|
||
/**
|
||
* Main entry point: detect and add missing contributors.
|
||
*/
|
||
const main = async () => {
|
||
console.log('🚀 Starting add missing contributors script');
|
||
console.log('='.repeat(50));
|
||
|
||
try {
|
||
console.log('\n📋 Step 1: Detecting missing contributors...');
|
||
const missingContributors = getMissingContributors();
|
||
|
||
if (missingContributors.length === 0) {
|
||
console.log('🎉 No missing contributors found! All contributors are properly recognized.');
|
||
return { processed: 0, added: 0, failed: 0 };
|
||
}
|
||
|
||
console.log(`\n🔄 Processing ${missingContributors.length} missing contributors...`);
|
||
|
||
let processed = 0;
|
||
let added = 0;
|
||
let failed = 0;
|
||
|
||
for (const username of missingContributors) {
|
||
console.log(`\n${'─'.repeat(30)}`);
|
||
console.log(`👤 Processing contributor: ${username}`);
|
||
|
||
processed++;
|
||
|
||
try {
|
||
const { added: deltaAdded, failed: deltaFailed } = await processContributor(username);
|
||
added += deltaAdded;
|
||
failed += deltaFailed;
|
||
} catch (error) {
|
||
// Re-throw system-level errors (rate limit, network, SIGINT)
|
||
console.error(`💥 System error processing ${username}:`, error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
return { processed, added, failed };
|
||
} catch (error) {
|
||
console.error('\n💥 Fatal error in main execution:', error.message);
|
||
console.error('🛑 Script execution stopped');
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Print a summary report of the run.
|
||
* @param {{processed:number, added:number, failed:number}} results
|
||
*/
|
||
const printSummaryReport = (results) => {
|
||
const { processed, added, failed } = results;
|
||
|
||
console.log('\n' + '='.repeat(50));
|
||
console.log('📊 EXECUTION SUMMARY');
|
||
console.log('='.repeat(50));
|
||
|
||
console.log(`📋 Total contributors processed: ${processed}`);
|
||
console.log(`✅ Successfully added: ${added}`);
|
||
console.log(`❌ Failed to add: ${failed}`);
|
||
|
||
if (processed === 0) {
|
||
console.log('\n🎉 SUCCESS: No missing contributors found - all contributors are properly recognized!');
|
||
} else if (failed === 0) {
|
||
console.log('\n🎉 SUCCESS: All missing contributors have been successfully added!');
|
||
console.log('💡 Next steps: Review the updated .all-contributorsrc file and commit the changes.');
|
||
} else if (added > 0) {
|
||
console.log('\n⚠️ PARTIAL SUCCESS: Some contributors were added, but some failed.');
|
||
console.log(`💡 ${added} contributors were successfully added.`);
|
||
console.log(`🔄 ${failed} contributors failed - check the error messages above for details.`);
|
||
console.log('💡 You may want to run the script again to retry failed contributors.');
|
||
} else {
|
||
console.log('\n❌ FAILURE: No contributors could be added.');
|
||
console.log('💡 Check the error messages above for troubleshooting guidance.');
|
||
console.log('💡 Common issues: missing GITHUB_TOKEN, network problems, or API rate limits.');
|
||
}
|
||
|
||
console.log('\n📝 ACTIONABLE NEXT STEPS:');
|
||
if (added > 0) {
|
||
console.log('• Review the updated .all-contributorsrc file');
|
||
console.log('• Commit and push the changes to update the README');
|
||
console.log('• Consider running "npm run contributors:generate" to update the README');
|
||
}
|
||
if (failed > 0) {
|
||
console.log('• Check error messages above for specific failure reasons');
|
||
console.log('• Verify GITHUB_TOKEN is set and has appropriate permissions');
|
||
console.log('• Consider running the script again after resolving issues');
|
||
}
|
||
if (processed === 0) {
|
||
console.log('• No action needed - all contributors are already recognized!');
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(50));
|
||
};
|
||
|
||
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
||
try {
|
||
const results = await main();
|
||
printSummaryReport(results);
|
||
|
||
if (results.failed > 0 && results.added === 0) {
|
||
process.exit(1);
|
||
} else if (results.failed > 0) {
|
||
process.exit(2);
|
||
} else {
|
||
process.exit(0);
|
||
}
|
||
} catch (error) {
|
||
console.error('\n💥 Script execution failed:', error.message);
|
||
console.log('\n📝 TROUBLESHOOTING TIPS:');
|
||
console.log('• Ensure you are in a git repository');
|
||
console.log('• Verify all-contributors-cli is installed');
|
||
console.log('• Check that .all-contributorsrc file exists');
|
||
console.log('• Ensure GITHUB_TOKEN environment variable is set');
|
||
process.exit(1);
|
||
}
|
||
}
|