feat: initial commit - GCP project cleaner CLI
This commit is contained in:
116
index.ts
Normal file
116
index.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user