Files
awesome/lib/db-download.js
2025-10-26 13:48:23 +01:00

365 lines
9.9 KiB
JavaScript
Raw Permalink 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.
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
const inquirer = require('inquirer');
const chalk = require('chalk');
const ora = require('ora');
const { purpleGold, pinkPurple, goldPink, sectionHeader } = require('./banner');
const DB_DIR = path.join(os.homedir(), '.awesome');
const DB_FILE = path.join(DB_DIR, 'awesome.db');
// Check if GitHub CLI is installed
function checkGhCli() {
try {
execSync('gh --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
// Check if authenticated with GitHub CLI
function checkGhAuth() {
try {
execSync('gh auth status', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
// Get repository from git remote
function getRepository() {
try {
const remote = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
const match = remote.match(/github\.com[:/]([^/]+\/[^/]+?)(\.git)?$/);
if (match) {
return match[1];
}
} catch {
// Not a git repository or no remote
}
return null;
}
// Fetch workflow runs
async function fetchWorkflowRuns(repo, limit = 10) {
try {
const output = execSync(
`gh api -H "Accept: application/vnd.github+json" "/repos/${repo}/actions/workflows/build-database.yml/runs?per_page=${limit}&status=success"`,
{ encoding: 'utf-8' }
);
const data = JSON.parse(output);
return data.workflow_runs || [];
} catch (error) {
throw new Error(`Failed to fetch workflow runs: ${error.message}`);
}
}
// Fetch artifacts for a run
async function fetchArtifacts(repo, runId) {
try {
const output = execSync(
`gh api -H "Accept: application/vnd.github+json" "/repos/${repo}/actions/runs/${runId}/artifacts"`,
{ encoding: 'utf-8' }
);
const data = JSON.parse(output);
return data.artifacts || [];
} catch (error) {
throw new Error(`Failed to fetch artifacts: ${error.message}`);
}
}
// Format date
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// Format size
function formatSize(bytes) {
const mb = bytes / (1024 * 1024);
return `${mb.toFixed(1)} MB`;
}
// List available databases
async function listDatabases(repo) {
const spinner = ora(chalk.hex('#DA22FF')('Fetching available databases...')).start();
try {
const runs = await fetchWorkflowRuns(repo, 10);
if (runs.length === 0) {
spinner.fail(chalk.red('No database builds found'));
return null;
}
// Fetch artifacts for each run
const runsWithArtifacts = [];
for (const run of runs) {
const artifacts = await fetchArtifacts(repo, run.id);
const dbArtifact = artifacts.find(a => a.name.startsWith('awesome-database'));
if (dbArtifact) {
runsWithArtifacts.push({
runId: run.id,
createdAt: run.created_at,
sha: run.head_sha.substring(0, 7),
artifact: dbArtifact
});
}
}
spinner.succeed(chalk.green(`Found ${runsWithArtifacts.length} available databases`));
if (runsWithArtifacts.length === 0) {
return null;
}
return runsWithArtifacts;
} catch (error) {
spinner.fail(chalk.red(error.message));
return null;
}
}
// Download and install database
async function downloadDatabase(repo, runId, artifactName) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'awesome-db-'));
try {
// Download artifact
const spinner = ora(chalk.hex('#FF69B4')('Downloading database...')).start();
const downloadProcess = spawn('gh', ['run', 'download', runId, '-R', repo, '-D', tempDir], {
stdio: 'pipe'
});
await new Promise((resolve, reject) => {
downloadProcess.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Download failed with code ${code}`));
}
});
downloadProcess.on('error', reject);
});
spinner.succeed(chalk.green('Downloaded successfully'));
// Find database file
const files = fs.readdirSync(tempDir, { recursive: true, withFileTypes: true });
const dbFile = files.find(f => f.isFile() && f.name.endsWith('.db'));
if (!dbFile) {
throw new Error('Database file not found in artifact');
}
const dbPath = path.join(dbFile.path || tempDir, dbFile.name);
// Backup existing database
if (fs.existsSync(DB_FILE)) {
const backupFile = `${DB_FILE}.backup.${Date.now()}`;
console.log(chalk.yellow(`\n⚠️ Backing up existing database to:`));
console.log(chalk.gray(` ${backupFile}`));
fs.copyFileSync(DB_FILE, backupFile);
}
// Create directory if needed
if (!fs.existsSync(DB_DIR)) {
fs.mkdirSync(DB_DIR, { recursive: true });
}
// Copy database
fs.copyFileSync(dbPath, DB_FILE);
const size = fs.statSync(DB_FILE).size;
console.log(chalk.green(`\n✓ Database installed successfully!`));
console.log(chalk.gray(` Location: ${DB_FILE}`));
console.log(chalk.gray(` Size: ${formatSize(size)}`));
// Show metadata if available
const metadataFile = files.find(f => f.isFile() && f.name === 'metadata.json');
if (metadataFile) {
const metadataPath = path.join(metadataFile.path || tempDir, metadataFile.name);
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
console.log(chalk.hex('#FFD700')('\n📊 Build Information:'));
console.log(chalk.gray(` Build Date: ${metadata.build_date}`));
console.log(chalk.gray(` Total Lists: ${metadata.total_lists}`));
console.log(chalk.gray(` Total Repos: ${metadata.total_repos}`));
console.log(chalk.gray(` Total READMEs: ${metadata.total_readmes}`));
console.log(chalk.gray(` Index Mode: ${metadata.index_mode}`));
}
} finally {
// Cleanup temp directory
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
// Main function
async function manage() {
console.clear();
sectionHeader('DATABASE DOWNLOADER', '💾');
// Check prerequisites
if (!checkGhCli()) {
console.log(chalk.red('✗ GitHub CLI (gh) is not installed\n'));
console.log(chalk.gray('Install from: https://cli.github.com/\n'));
console.log(chalk.gray('Quick install:'));
console.log(chalk.gray(' • macOS: brew install gh'));
console.log(chalk.gray(' • Ubuntu: sudo apt install gh'));
console.log(chalk.gray(' • Windows: winget install GitHub.cli\n'));
return;
}
if (!checkGhAuth()) {
console.log(chalk.yellow('⚠️ Not authenticated with GitHub CLI\n'));
const { authenticate } = await inquirer.prompt([
{
type: 'confirm',
name: 'authenticate',
message: 'Authenticate now?',
default: true
}
]);
if (!authenticate) {
console.log(chalk.gray('\nCancelled'));
return;
}
try {
execSync('gh auth login', { stdio: 'inherit' });
} catch {
console.log(chalk.red('\n✗ Authentication failed'));
return;
}
console.log(chalk.green('\n✓ Authenticated successfully\n'));
}
// Get repository
let repo = getRepository();
if (!repo) {
const { inputRepo } = await inquirer.prompt([
{
type: 'input',
name: 'inputRepo',
message: purpleGold('Enter GitHub repository (owner/repo):'),
validate: (input) => {
if (!input.match(/^[^/]+\/[^/]+$/)) {
return 'Please enter in format: owner/repo';
}
return true;
}
}
]);
repo = inputRepo;
} else {
console.log(purpleGold(`Repository: ${repo}\n`));
}
// List databases
const databases = await listDatabases(repo);
if (!databases || databases.length === 0) {
console.log(chalk.yellow('\n⚠ No databases available for download'));
console.log(chalk.gray(' Database builds are created by GitHub Actions'));
console.log(chalk.gray(' Check the Actions tab in your repository\n'));
return;
}
// Show table
console.log(chalk.hex('#DA22FF')('\nAvailable Databases:\n'));
const Table = require('cli-table3');
const table = new Table({
head: [
chalk.hex('#DA22FF')('#'),
chalk.hex('#DA22FF')('Build Date'),
chalk.hex('#DA22FF')('Commit'),
chalk.hex('#DA22FF')('Size')
],
colWidths: [5, 25, 12, 12],
style: {
head: [],
border: ['gray']
}
});
databases.forEach((db, idx) => {
table.push([
chalk.gray(idx + 1),
chalk.hex('#FF69B4')(formatDate(db.createdAt)),
chalk.hex('#FFD700')(db.sha),
chalk.hex('#9733EE')(formatSize(db.artifact.size_in_bytes))
]);
});
console.log(table.toString());
// Select database
const choices = [
...databases.map((db, idx) => ({
name: `${idx + 1}. ${formatDate(db.createdAt)} (${db.sha}) - ${formatSize(db.artifact.size_in_bytes)}`,
value: idx
})),
new inquirer.Separator(),
{ name: chalk.gray('← Cancel'), value: -1 }
];
const { selection } = await inquirer.prompt([
{
type: 'list',
name: 'selection',
message: 'Select a database to download:',
choices: choices,
pageSize: 12
}
]);
if (selection === -1) {
console.log(chalk.gray('\nCancelled'));
return;
}
const selectedDb = databases[selection];
// Confirm download
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Download database from ${formatDate(selectedDb.createdAt)}?`,
default: true
}
]);
if (!confirm) {
console.log(chalk.gray('\nCancelled'));
return;
}
// Download and install
await downloadDatabase(repo, selectedDb.runId, selectedDb.artifact.name);
console.log(chalk.hex('#FFD700')('\n🎉 Ready to use!'));
console.log(chalk.gray(' Run: ./awesome\n'));
}
module.exports = {
manage
};