|
|
|
@@ -0,0 +1,167 @@
|
|
|
|
|
/**
|
|
|
|
|
* @copyright NHCarrigan
|
|
|
|
|
* @license Naomi's Public License
|
|
|
|
|
* @author Naomi Carrigan
|
|
|
|
|
*/
|
|
|
|
|
import {
|
|
|
|
|
S3Client, ListObjectsV2Command, DeleteObjectsCommand,
|
|
|
|
|
type ListObjectsV2CommandOutput,
|
|
|
|
|
} from "@aws-sdk/client-s3";
|
|
|
|
|
import { input, confirm } from "@inquirer/prompts";
|
|
|
|
|
import { SingleBar, Presets } from "cli-progress";
|
|
|
|
|
|
|
|
|
|
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
|
|
|
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
|
|
|
|
|
|
|
|
if (accessKeyId === undefined || secretAccessKey === undefined) {
|
|
|
|
|
throw new Error("AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY is not set");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get bucket name
|
|
|
|
|
const bucketName = await input({
|
|
|
|
|
message: "Enter the S3 bucket name:",
|
|
|
|
|
validate: (value) => {
|
|
|
|
|
if (value.trim() === "") {
|
|
|
|
|
return "Bucket name cannot be empty";
|
|
|
|
|
}
|
|
|
|
|
// Basic S3 bucket name validation
|
|
|
|
|
if (!/^[\d.a-z-]+$/.test(value)) {
|
|
|
|
|
return `Bucket name can only contain lowercase letters, numbers, dots, and hyphens`;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create S3 client
|
|
|
|
|
const s3 = new S3Client({
|
|
|
|
|
credentials: { accessKeyId, secretAccessKey },
|
|
|
|
|
endpoint: "https://hel1.your-objectstorage.com",
|
|
|
|
|
region: "hel1",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// First, count total objects to delete
|
|
|
|
|
console.log("\nCounting objects in bucket...");
|
|
|
|
|
let totalObjects = 0;
|
|
|
|
|
let continuationToken: string | null = null;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
const listCommand = new ListObjectsV2Command({
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Bucket: bucketName,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
ContinuationToken: continuationToken ?? undefined,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
MaxKeys: 1000,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const listResponse: ListObjectsV2CommandOutput = await s3.send(listCommand);
|
|
|
|
|
totalObjects = totalObjects + (listResponse.Contents?.length ?? 0);
|
|
|
|
|
continuationToken = listResponse.NextContinuationToken ?? null;
|
|
|
|
|
} while (continuationToken !== null);
|
|
|
|
|
|
|
|
|
|
if (totalObjects === 0) {
|
|
|
|
|
console.log("✨ No files found in the bucket!");
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`\nFound ${totalObjects.toString()} object(s) to delete.`);
|
|
|
|
|
|
|
|
|
|
// Safety confirmation
|
|
|
|
|
console.log(`\n⚠️ WARNING: This will DELETE ALL ${totalObjects.toString()} FILES in the bucket "${bucketName}"`);
|
|
|
|
|
console.log("This action cannot be undone!\n");
|
|
|
|
|
|
|
|
|
|
// First confirmation - type bucket name
|
|
|
|
|
await input({
|
|
|
|
|
message: `Type the bucket name "${bucketName}" to confirm deletion:`,
|
|
|
|
|
validate: (value) => {
|
|
|
|
|
if (value !== bucketName) {
|
|
|
|
|
return `Please type "${bucketName}" exactly to confirm`;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Second confirmation - yes/no
|
|
|
|
|
const finalConfirm = await confirm({
|
|
|
|
|
default: false,
|
|
|
|
|
message: "Are you ABSOLUTELY sure you want to delete all files?",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!finalConfirm) {
|
|
|
|
|
console.log("❌ Operation cancelled. No files were deleted.");
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("\n🚀 Starting deletion process...\n");
|
|
|
|
|
|
|
|
|
|
// Initialize progress bar
|
|
|
|
|
const bar = new SingleBar({}, Presets.shades_classic);
|
|
|
|
|
bar.start(totalObjects, 0);
|
|
|
|
|
|
|
|
|
|
let successCount = 0;
|
|
|
|
|
let errorCount = 0;
|
|
|
|
|
continuationToken = null;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// List objects in the bucket
|
|
|
|
|
const listCommand = new ListObjectsV2Command({
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Bucket: bucketName,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
ContinuationToken: continuationToken ?? undefined,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
MaxKeys: 1000,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const listResponse: ListObjectsV2CommandOutput = await s3.send(listCommand);
|
|
|
|
|
|
|
|
|
|
if (!listResponse.Contents || listResponse.Contents.length === 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare objects for deletion
|
|
|
|
|
const objectsToDelete = listResponse.Contents.
|
|
|
|
|
filter((object) => {
|
|
|
|
|
return object.Key !== undefined;
|
|
|
|
|
}).
|
|
|
|
|
map((object) => {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
return { Key: object.Key };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Delete objects in batch
|
|
|
|
|
const deleteCommand = new DeleteObjectsCommand({
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Bucket: bucketName,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Delete: {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Objects: objectsToDelete,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- AWS SDK
|
|
|
|
|
Quiet: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const deleteResponse = await s3.send(deleteCommand);
|
|
|
|
|
|
|
|
|
|
const deletedCount = deleteResponse.Deleted?.length ?? 0;
|
|
|
|
|
successCount = successCount + deletedCount;
|
|
|
|
|
|
|
|
|
|
// Check for errors
|
|
|
|
|
if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
|
|
|
|
|
errorCount = errorCount + deleteResponse.Errors.length;
|
|
|
|
|
for (const error of deleteResponse.Errors) {
|
|
|
|
|
console.error(`\n⚠️ Error deleting ${error.Key ?? ""}: ${error.Message ?? ""}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bar.increment(deletedCount);
|
|
|
|
|
|
|
|
|
|
continuationToken = listResponse.NextContinuationToken ?? null;
|
|
|
|
|
} while (continuationToken !== null);
|
|
|
|
|
|
|
|
|
|
bar.stop();
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`\n✅ Delete complete! ${successCount.toString()} succeeded, ${errorCount.toString()} failed.`,
|
|
|
|
|
);
|