import { checkbox, confirm } from '@inquirer/prompts'; import chalk from 'chalk'; import ora from 'ora'; import { execa } from 'execa'; interface GCPProject { projectId: string; name: string; projectNumber: string; lifecycleState: string; createTime: string; } async function checkAuth() { const spinner = ora('Checking GCP authentication...').start(); try { await execa('gcloud', ['auth', 'print-access-token']); spinner.succeed('Authenticated with GCP'); } catch (error) { spinner.fail('Not authenticated with GCP. Please run "gcloud auth login".'); process.exit(1); } } async function listProjects(): Promise { const spinner = ora('Fetching GCP projects...').start(); try { const { stdout } = await execa('gcloud', ['projects', 'list', '--format=json', '--filter=lifecycleState:ACTIVE' ]); const projects: GCPProject[] = JSON.parse(stdout); spinner.succeed(`Found ${projects.length} active projects`); return projects; } catch (error) { spinner.fail('Failed to fetch projects'); console.error(error); process.exit(1); } } function formatProjectChoice(project: GCPProject) { const createdDate = new Date(project.createTime); const now = new Date(); const ageInDays = Math.floor((now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24)); let ageStr = `${ageInDays} days old`; if (ageInDays > 365) { ageStr = chalk.red(`${(ageInDays / 365).toFixed(1)} years old`); } else if (ageInDays > 30) { ageStr = chalk.yellow(ageStr); } else { ageStr = chalk.green(ageStr); } return { name: `${project.name} (${chalk.cyan(project.projectId)}) - ${ageStr}`, value: project.projectId, short: project.projectId, }; } async function main() { console.log(chalk.bold.blue('\nGCP Project Cleaner\n')); await checkAuth(); const projects = await listProjects(); if (projects.length === 0) { console.log(chalk.yellow('No active projects found.')); return; } const selectedProjectIds = await checkbox({ message: 'Select projects to DELETE (Space to select, Enter to confirm):', choices: projects.map(formatProjectChoice), }); if (selectedProjectIds.length === 0) { console.log(chalk.yellow('No projects selected. Exiting.')); return; } console.log(chalk.red.bold('\nWARNING: The following projects will be scheduled for deletion:')); selectedProjectIds.forEach(id => console.log(chalk.red(` - ${id}`))); const confirmed = await confirm({ message: chalk.red('Are you absolutely sure you want to delete these projects?'), default: false, }); if (!confirmed) { console.log(chalk.yellow('Deletion cancelled.')); return; } console.log(); for (const projectId of selectedProjectIds) { const spinner = ora(`Deleting project: ${projectId}...`).start(); try { await execa('gcloud', ['projects', 'delete', projectId, '--quiet']); spinner.succeed(chalk.green(`Successfully scheduled deletion for ${projectId}`)); } catch (error: any) { spinner.fail(chalk.red(`Failed to delete ${projectId}: ${error.message}`)); } } console.log(chalk.bold.green('\nProcess completed.\n')); } main().catch(err => { console.error(chalk.red('An unexpected error occurred:'), err); process.exit(1); });