117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
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);
|
|
});
|