@supapool/cli
v0.4.1
Published
Acquire and release disposable Supabase instances
Readme
@supapool/cli
Disposable Supabase instances for coding agents, tests, and local development.
npx @supapool/cli run -- npm run devSupapool leases a clean, isolated Supabase instance from a warm pool, applies
your repository's supabase/migrations, injects standard Supabase and
Postgres environment variables into the wrapped command, renews the lease
while the command runs, and releases the instance when it exits. Your
repository's environment files are never modified.
Requires Node.js 18 or newer (needs built-in fetch). The hosted beta is free.
Commands
The CLI has two commands.
run
npx @supapool/cli run -- <command> [args...]Everything after -- runs with a leased instance in its environment. Examples:
npx @supapool/cli run -- pnpm test # CI or a one-shot test run
npx @supapool/cli run -- npm run dev # a dev server for a working sessionThe lease lives exactly as long as the wrapped command. When the command exits, the instance is released and its slot is wiped for the next caller. If the process dies without releasing, the lease stops renewing and expires on its own.
For local development, embed the wrapper in the dev script so plain
npm run dev works for everyone, including coding agents:
{
"scripts": {
"dev": "supapool run -- next dev"
},
"devDependencies": {
"@supapool/cli": "latest"
}
}Embed it at one level only. run always acquires a fresh instance, even
when invoked inside an existing lease, so a wrapped script wrapped again
would lease two instances.
On first use, run opens GitHub sign-in and saves an API key automatically,
so login is rarely needed as a separate step.
login
npx @supapool/cli loginOpens GitHub sign-in in the browser and saves the session and a default API
key to ~/.config/supapool/config.json with mode 0600. The key secret is
not printed.
What gets injected
The wrapped command receives standard Supabase credentials:
SUPABASE_URLSUPABASE_ANON_KEYandSUPABASE_PUBLISHABLE_KEYSUPABASE_SERVICE_ROLE_KEYandSUPABASE_SECRET_KEYDATABASE_URLSUPAPOOL_INSTANCE_ID
The same values are mirrored to the aliases most stacks expect:
- Framework public prefixes:
NEXT_PUBLIC_,VITE_,PUBLIC_,EXPO_PUBLIC_,REACT_APP_,GATSBY_, andNUXT_PUBLIC_variants of the URL and publishable key. - Database URL aliases:
SUPABASE_DB_URL,DIRECT_URL,POSTGRES_URL,POSTGRES_PRISMA_URL,POSTGRES_URL_NON_POOLING, andDB_URL. - Standard connection variables:
PGHOST,PGPORT,PGDATABASE,PGUSER,PGPASSWORD, and theirPOSTGRES_*equivalents.
Existing custom variables whose names end in recognized Supabase URL or key names are also redirected to the leased instance. Secret and service-role keys are never assigned to browser-public variables.
Migrations
Before starting your command, run applies every .sql file in
supabase/migrations in filename order and records them in
supabase_migrations.schema_migrations, matching the Supabase CLI
convention. Repositories without a migrations directory skip this step.
The library API does the same: acquire({ root }) applies the repository's
migrations before returning the instance, and releases the instance if a
migration fails. Omit root to acquire without touching the database.
Migration statements run in file order without an implicit transaction.
This supports PostgreSQL commands that require autocommit, including
CREATE INDEX CONCURRENTLY. A session advisory lock serializes concurrent
appliers, and a migration is recorded only after all of its statements
succeed. Migration files may use their own BEGIN and COMMIT when they need
transactional behavior. Failures name the file and line, for example
Migration 20260722223000_prefab_toolbox.sql failed (line 3): ....
Seeding
After migrations, run executes the ordered seed files configured by
[db.seed].sql_paths in supabase/config.toml. File paths, directory paths,
and glob patterns are supported. Without configuration, the default remains
supabase/seed.sql. Setting [db.seed].enabled to false disables seeding.
Seed statements use the same session lock and autocommit behavior as
migrations, and each seed file is recorded after every statement succeeds.
A seed file may manage its own transaction when needed. Failures name the
file and line.
Leases
Every instance is a lease with a TTL, 30 minutes by default. The CLI renews the lease every 5 minutes while the wrapped command is alive. There is no manual renew: a lease is kept alive by a running process and expires when renewals stop. Expired leases are garbage collected and their slots wiped, so nothing stored in an instance survives release.
Set SUPAPOOL_TTL_SECONDS to change the TTL for a run.
CI
CI jobs skip the browser login. Sign in once on a laptop, copy the API key
from ~/.config/supapool/config.json into a CI secret, and expose it as
SUPAPOOL_API_KEY:
- run: npx @supapool/cli run -- pnpm test
env:
SUPAPOOL_API_KEY: ${{ secrets.SUPAPOOL_API_KEY }}GitHub Actions runs get an owner label derived from the run ID, so leases are traceable per workflow run.
Programmatic API
The package is also a library. withInstance acquires a lease, renews it
while your callback runs, and always releases it when the callback returns or
throws:
import { withInstance } from '@supapool/cli'
await withInstance(async (instance) => {
const { SUPABASE_URL, SUPABASE_ANON_KEY, DATABASE_URL } = instance.env
// use the instance; the lease stays renewed until this returns
})instance.env contains the full injected variable set described above.
Options such as ttlSeconds pass through to acquisition:
await withInstance(fn, { ttlSeconds: 600 })The lifecycle primitives are exported for code that manages its own lease boundaries:
import { acquire, release, startRenewer } from '@supapool/cli'
const instance = await acquire()
const stop = startRenewer(instance)
try {
console.log(instance.env.SUPABASE_URL)
} finally {
stop()
await release(instance)
}renew(instance) is also exported for a single manual renewal, and
runWithInstance(command, args, options) is the engine behind supapool run.
Environment variables
SUPAPOOL_API_KEY: API key for headless use. Overrides the saved config.SUPAPOOL_API_URL: hosted API endpoint override for CI.SUPAPOOL_TTL_SECONDS: lease TTL, default 1800.SUPAPOOL_TELEMETRY_DISABLED=1: disables run telemetry.
Support
The hosted beta is free. Email [email protected] for an immediate automated response with a fix or a request for the information needed to solve it. See supapool.io for docs and community support.
