t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/scripts/cleanup-neon-projects.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

96 lines
2.8 KiB
TypeScript

#!/usr/bin/env bun
/**
* Delete every Neon project visible to `NEON_API_KEY`.
*
* Usage:
* doppler run -c dev --project alchemy-v2 -- bun packages/alchemy/scripts/cleanup-neon-projects.ts
* DRY_RUN=1 doppler run -c dev --project alchemy-v2 -- bun packages/alchemy/scripts/cleanup-neon-projects.ts
* CONCURRENCY=4 doppler run -c dev --project alchemy-v2 -- bun packages/alchemy/scripts/cleanup-neon-projects.ts
*
* Environment:
* NEON_API_KEY Required. Injected by Doppler (`alchemy-v2` / `dev`) or `.env` via `bun download:env`.
* DRY_RUN Set to `1` to list projects without deleting.
* CONCURRENCY Parallel deletes (default 4).
*/
import {
CredentialsFromEnv,
deleteProject,
listProjects,
Retry,
} from "@distilled.cloud/neon";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Stream from "effect/Stream";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
const DRY_RUN = process.env.DRY_RUN === "1";
const CONCURRENCY = Math.max(1, Number(process.env.CONCURRENCY ?? 4));
if (!process.env.NEON_API_KEY) {
console.error("NEON_API_KEY is not set");
process.exit(1);
}
console.log(
`→ neon cleanup dryRun=${DRY_RUN} concurrency=${CONCURRENCY} (listing projects…)`,
);
let total = 0;
let ok = 0;
let fail = 0;
const program = listProjects.pages({ limit: 400 }).pipe(
Stream.tap((page) =>
Effect.sync(() => console.log(`→ page: ${page.projects.length} projects`)),
),
Stream.flatMap((page) => Stream.fromIterable(page.projects)),
Stream.tap((p: { id: string; name: string }) =>
Effect.sync(() => {
total += 1;
console.log(` - ${p.name} (${p.id})`);
}),
),
Stream.mapEffect(
(p) =>
DRY_RUN
? Effect.void
: deleteProject({ project_id: p.id }).pipe(
Effect.tap(() =>
Effect.sync(() => {
ok += 1;
console.log(`✓ deleted ${p.name} (${ok})`);
}),
),
Effect.catchTag("NotFound", () =>
Effect.sync(() => {
ok += 1;
console.log(`✓ already gone ${p.name}`);
}),
),
Effect.catch((e) =>
Effect.sync(() => {
fail += 1;
console.error(`✗ ${p.name} (${p.id}): ${String(e)}`);
}),
),
),
{ concurrency: CONCURRENCY },
),
Stream.runDrain,
Effect.tap(() =>
Effect.sync(() =>
console.log(`→ done: total=${total} deleted=${ok} failed=${fail}`),
),
),
);
const runtime = Layer.mergeAll(
CredentialsFromEnv,
FetchHttpClient.layer,
Layer.succeed(Retry.Retry, { while: () => false }),
);
Effect.runPromise(program.pipe(Effect.provide(runtime))).catch((error) => {
console.error(error);
process.exit(1);
});