docs: Add comprehensive package contribution guide and validation tooling

- Add bilingual README with English/Turkish language switcher
- Create detailed package contribution guidelines with platform-specific naming conventions
- Add validation script for verifying package presets against database
- Include best practices and example PR format for contributors
- Add tsx dependency for running TypeScript validation scripts
- Document validation workflow with manual and automated verification options
This commit is contained in:
Yusuf İpek
2025-11-23 14:26:07 +03:00
parent 24c80559c8
commit 9266de1f3e
14 changed files with 1594 additions and 197 deletions
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env node
/**
* Validate package names in recommendation presets against the database
*
* Usage:
* node scripts/validate-presets.js --all
* node scripts/validate-presets.js windows macos
* node scripts/validate-presets.js ubuntu
*/
const fetch = require('node-fetch');
// Import the presets
const { PACKAGE_PRESETS } = require('../src/data/recommendationPresets.ts');
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
// Platform IDs
const PLATFORMS = ['windows', 'macos', 'ubuntu', 'debian', 'arch', 'fedora'];
async function checkPackageExists(platformId, packageName) {
try {
const response = await fetch(
`${API_BASE_URL}/api/packages?platform_id=${platformId}&search=${encodeURIComponent(packageName)}&limit=10`
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
// Check for exact match (case-insensitive)
const exactMatch = data.packages?.find(
pkg => pkg.name.toLowerCase() === packageName.toLowerCase()
);
return {
exists: !!exactMatch,
foundName: exactMatch?.name || null,
similarMatches: data.packages?.slice(0, 3).map(p => p.name) || []
};
} catch (error) {
console.error(`Error checking ${packageName} on ${platformId}:`, error.message);
return { exists: false, error: error.message };
}
}
async function validatePlatform(platformId) {
console.log(`\n${'='.repeat(60)}`);
console.log(`Validating ${platformId.toUpperCase()}`);
console.log('='.repeat(60));
const platformPresets = PACKAGE_PRESETS[platformId];
if (!platformPresets) {
console.log(`❌ No presets found for platform: ${platformId}`);
return { total: 0, found: 0, missing: [] };
}
const results = {
total: 0,
found: 0,
missing: []
};
// Check each category
for (const [category, packages] of Object.entries(platformPresets)) {
console.log(`\n📁 Category: ${category}`);
for (const packageName of packages) {
results.total++;
const check = await checkPackageExists(platformId, packageName);
if (check.error) {
console.log(` ⚠️ ${packageName} - Error: ${check.error}`);
results.missing.push({ category, packageName, reason: 'API Error' });
} else if (check.exists) {
console.log(`${packageName}${check.foundName !== packageName ? ` (found as: ${check.foundName})` : ''}`);
results.found++;
} else {
console.log(`${packageName} - NOT FOUND`);
if (check.similarMatches?.length > 0) {
console.log(` Similar: ${check.similarMatches.join(', ')}`);
}
results.missing.push({
category,
packageName,
similar: check.similarMatches
});
}
// Small delay to avoid overwhelming the API
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage:');
console.log(' node scripts/validate-presets.js --all');
console.log(' node scripts/validate-presets.js windows macos ubuntu');
console.log('\nAvailable platforms:', PLATFORMS.join(', '));
process.exit(1);
}
let platformsToCheck = [];
if (args.includes('--all')) {
platformsToCheck = PLATFORMS;
} else {
// Validate platform names
for (const platform of args) {
if (!PLATFORMS.includes(platform)) {
console.error(`❌ Invalid platform: ${platform}`);
console.log('Available platforms:', PLATFORMS.join(', '));
process.exit(1);
}
}
platformsToCheck = args;
}
console.log(`\n🔍 Validating package presets for: ${platformsToCheck.join(', ')}\n`);
const allResults = {};
for (const platform of platformsToCheck) {
const results = await validatePlatform(platform);
allResults[platform] = results;
}
// Summary
console.log(`\n${'='.repeat(60)}`);
console.log('SUMMARY');
console.log('='.repeat(60));
let totalPackages = 0;
let totalFound = 0;
let totalMissing = 0;
for (const [platform, results] of Object.entries(allResults)) {
totalPackages += results.total;
totalFound += results.found;
totalMissing += results.missing.length;
const successRate = results.total > 0
? ((results.found / results.total) * 100).toFixed(1)
: 0;
console.log(`\n${platform.toUpperCase()}:`);
console.log(` Total: ${results.total}`);
console.log(` Found: ${results.found} (${successRate}%)`);
console.log(` Missing: ${results.missing.length}`);
if (results.missing.length > 0) {
console.log(` Missing packages:`);
for (const { category, packageName, similar } of results.missing) {
console.log(` - ${packageName} (${category})`);
if (similar?.length > 0) {
console.log(` Try: ${similar.join(', ')}`);
}
}
}
}
console.log(`\n${'='.repeat(60)}`);
console.log(`OVERALL: ${totalFound}/${totalPackages} packages found (${((totalFound / totalPackages) * 100).toFixed(1)}%)`);
console.log('='.repeat(60));
if (totalMissing > 0) {
console.log(`\n⚠️ Found ${totalMissing} missing packages. Review the output above and update recommendationPresets.ts`);
process.exit(1);
} else {
console.log('\n✅ All packages validated successfully!');
process.exit(0);
}
}
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
+194
View File
@@ -0,0 +1,194 @@
/**
* Validate package names in recommendation presets against the database
*
* Usage:
* tsx scripts/validate-presets.ts --all
* tsx scripts/validate-presets.ts windows macos
* tsx scripts/validate-presets.ts ubuntu
*/
import { PACKAGE_PRESETS } from '../src/data/recommendationPresets';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3002';
// Platform IDs
const PLATFORMS = ['windows', 'macos', 'ubuntu', 'debian', 'arch', 'fedora'] as const;
type PlatformId = typeof PLATFORMS[number];
interface PackageCheckResult {
exists: boolean;
foundName?: string;
similarMatches?: string[];
error?: string;
}
async function checkPackageExists(
platformId: string,
packageName: string
): Promise<PackageCheckResult> {
try {
const response = await fetch(
`${API_BASE_URL}/api/packages?platform_id=${platformId}&search=${encodeURIComponent(packageName)}&limit=10`
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
// Check for exact match (case-insensitive)
const exactMatch = data.packages?.find(
(pkg: any) => pkg.name.toLowerCase() === packageName.toLowerCase()
);
return {
exists: !!exactMatch,
foundName: exactMatch?.name || undefined,
similarMatches: data.packages?.slice(0, 3).map((p: any) => p.name) || []
};
} catch (error: any) {
console.error(`Error checking ${packageName} on ${platformId}:`, error.message);
return { exists: false, error: error.message };
}
}
async function validatePlatform(platformId: PlatformId) {
console.log(`\n${'='.repeat(60)}`);
console.log(`Validating ${platformId.toUpperCase()}`);
console.log('='.repeat(60));
const platformPresets = PACKAGE_PRESETS[platformId];
if (!platformPresets) {
console.log(`❌ No presets found for platform: ${platformId}`);
return { total: 0, found: 0, missing: [] as any[] };
}
const results = {
total: 0,
found: 0,
missing: [] as { category: string; packageName: string; similar?: string[]; reason?: string }[]
};
// Check each category
for (const [category, packages] of Object.entries(platformPresets)) {
console.log(`\n📁 Category: ${category}`);
for (const packageName of packages) {
results.total++;
const check = await checkPackageExists(platformId, packageName);
if (check.error) {
console.log(` ⚠️ ${packageName} - Error: ${check.error}`);
results.missing.push({ category, packageName, reason: 'API Error' });
} else if (check.exists) {
console.log(`${packageName}${check.foundName !== packageName ? ` (found as: ${check.foundName})` : ''}`);
results.found++;
} else {
console.log(`${packageName} - NOT FOUND`);
if (check.similarMatches && check.similarMatches.length > 0) {
console.log(` Similar: ${check.similarMatches.join(', ')}`);
}
results.missing.push({
category,
packageName,
similar: check.similarMatches
});
}
// Delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return results;
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage:');
console.log(' tsx scripts/validate-presets.ts --all');
console.log(' tsx scripts/validate-presets.ts windows macos ubuntu');
console.log('\nAvailable platforms:', PLATFORMS.join(', '));
process.exit(1);
}
let platformsToCheck: PlatformId[] = [];
if (args.includes('--all')) {
platformsToCheck = [...PLATFORMS];
} else {
// Validate platform names
for (const platform of args) {
if (!PLATFORMS.includes(platform as any)) {
console.error(`❌ Invalid platform: ${platform}`);
console.log('Available platforms:', PLATFORMS.join(', '));
process.exit(1);
}
}
platformsToCheck = args as PlatformId[];
}
console.log(`\n🔍 Validating package presets for: ${platformsToCheck.join(', ')}\n`);
const allResults: Record<string, any> = {};
for (const platform of platformsToCheck) {
const results = await validatePlatform(platform);
allResults[platform] = results;
}
// Summary
console.log(`\n${'='.repeat(60)}`);
console.log('SUMMARY');
console.log('='.repeat(60));
let totalPackages = 0;
let totalFound = 0;
let totalMissing = 0;
for (const [platform, results] of Object.entries(allResults)) {
totalPackages += results.total;
totalFound += results.found;
totalMissing += results.missing.length;
const successRate = results.total > 0
? ((results.found / results.total) * 100).toFixed(1)
: '0';
console.log(`\n${platform.toUpperCase()}:`);
console.log(` Total: ${results.total}`);
console.log(` Found: ${results.found} (${successRate}%)`);
console.log(` Missing: ${results.missing.length}`);
if (results.missing.length > 0) {
console.log(` Missing packages:`);
for (const { category, packageName, similar } of results.missing) {
console.log(` - ${packageName} (${category})`);
if (similar && similar.length > 0) {
console.log(` Try: ${similar.join(', ')}`);
}
}
}
}
console.log(`\n${'='.repeat(60)}`);
console.log(`OVERALL: ${totalFound}/${totalPackages} packages found (${((totalFound / totalPackages) * 100).toFixed(1)}%)`);
console.log('='.repeat(60));
if (totalMissing > 0) {
console.log(`\n⚠️ Found ${totalMissing} missing packages. Review the output above and update recommendationPresets.ts`);
process.exit(1);
} else {
console.log('\n✅ All packages validated successfully!');
process.exit(0);
}
}
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});