1args=("$@")
2has_profile=false
3for ((i = 0; i < ${#args[@]}; i++)); do
4 case "${args[i]}" in
5 --) break ;;
6 --profile)
7 if [[ -z "${args[i+1]:-}" || "${args[i+1]}" == -* ]]; then
8 echo "Error: --profile requires a profile name." >&2
9 exit 1
10 fi
11 has_profile=true
12 ((i++))
13 ;;
14 --profile=*)
15 if [[ -z "${args[i]#--profile=}" ]]; then
16 echo "Error: --profile requires a profile name." >&2
17 exit 1
18 fi
19 has_profile=true
20 ;;
21 esac
22done
23if [[ "$has_profile" != true ]]; then
24 echo "Error: pass --profile <name> explicitly to nuke resources." >&2
25 exit 1
26fi
27
28# AWS.BackupSearch.SearchJob: AWS retains search-job records ~7 days; no delete API
29# Cloudflare.Cache.RegionalTieredCache / Cloudflare.Logs.RetentionFlag /
30# Cloudflare.AI.SecuritySettings: zone-singleton settings that always exist
31# on entitled zones — delete restores the pre-management value (a no-op
32# when enumerated), so the census would flag them forever.
33# Cloudflare.AI.Gateway "default": auto-provisioned by Cloudflare when an AI Search
34# instance is created; recreated on demand, so deleting it just churns.
35# Cloudflare.Email.Address alchemy-list-test@: standing test address — Cloudflare
36# refuses to delete an address for ~15 min after creation (code 2032), so the
37# EmailAddress test retains and re-adopts it instead of create/destroy churn.
38bun alchemy unsafe nuke --config ./stacks/nuke.ts \
39 --exclude 'AWS.BackupSearch.SearchJob' \
40 --exclude 'Cloudflare.Zone*' \
41 --exclude 'Cloudflare.Account*' \
42 --exclude 'Cloudflare.DNS*' \
43 --exclude 'Cloudflare.Ssl.UniversalSsl' \
44 --exclude 'Cloudflare.Cache.RegionalTieredCache' \
45 --exclude 'Cloudflare.Logs.RetentionFlag' \
46 --exclude 'Cloudflare.AI.SecuritySettings' \
47 --exclude 'Cloudflare.ApiToken.*' \
48 --exclude 'Cloudflare.Secret*' \
49 --exclude 'Cloudflare.Organization.*' \
50 --exclude 'AWS.IAM.User' \
51 --exclude 'AWS.IAM.SAMLProvider' \
52 --exclude 'AWS.IAM.OpenIDConnectProvider' \
53 --exclude 'AWS.IAM.AccountAlias' \
54 --exclude 'AWS.IAM.AccountPasswordPolicy' \
55 --exclude 'AWS.IAM.LoginProfile' \
56 --exclude 'AWS.IdentityCenter*' \
57 --exclude 'AWS.Organizations.*' \
58 --exclude 'AWS.IAM.ServiceLinkedRole' \
59 --exclude 'AWS.LakeFormation.*' \
60 --exclude 'AWS.Notifications.*' \
61 --exclude 'AWS.NotificationsContacts.*' \
62 --exclude 'AWS.ApiGateway.Account' \
63 --concurrency 32 \
64 --timeout 300 \
65 --filter 'resource.Type === "Cloudflare.Worker" && (resource.workerName?.startsWith("alchemy-state") || resource.workerName === "Api" || ["alchemy-website-preview","alchemy-website-main","alchemy-website-prod"].includes(resource.workerName))' \
66 --filter 'resource.Type === "AWS.IAM.Role" && (["alchemy-github-actions", "distilled-github-oidc-role"].includes(resource.roleName) || resource.roleName?.startsWith("AWSReservedSSO"))' \
67 --filter 'resource.Type === "AWS.S3.Bucket" && (String(resource.bucketName).startsWith("alchemy-state") || String(resource.bucketName).startsWith("alchemy-assets"))' \
68 --filter 'typeof resource.name === "string" && (resource.name.startsWith("DO-NOT-DELETE") || resource.name.startsWith("AppConfig.") || resource.name.startsWith("system_") || ["primary","AwsDataCatalog","DefaultConfiguration","Default","default","open-access","default.dax1.0"].includes(resource.name))' \
69 --filter 'resource.Type === "Cloudflare.AI.Gateway" && resource.gatewayId === "default"' \
70 --filter 'resource.Type === "Cloudflare.Email.Address" && resource.email === "alchemy-list-test@alchemy-test-2.us"' \
71 --filter 'String(resource.logGroupName).startsWith("/aws/vendedlogs/b2bi/")' \
72 --filter 'resource.Type === "Fly.Bucket" && resource.name === "fly-buovnrccm7qpb3be3utsdvlj6x"' \
73 "$@"