feat: initial commit - GCP project cleaner CLI

This commit is contained in:
2025-12-23 17:48:13 -05:00
commit 34941f3cf9
6 changed files with 1169 additions and 0 deletions

116
index.ts Normal file
View File

@@ -0,0 +1,116 @@
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<GCPProject[]> {
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);
});