Migrations from the CLI
Your database config is already a valid drizzle-kit Config — DrizzleLibSQLConfig and
its pg/mysql2 twins carry dialect / schema / out / dbCredentials as own fields. heximon migrations
turns that fact into a CLI: it finds your config file(s) on disk, loads your .env cascade, and drives the
stock drizzle-kit binary for you — no drizzle.config.ts to hand-write, no --config flag to remember.
pnpx heximon migrations generate
pnpx heximon migrations migrate
How detection works
DatabaseConfigDetector walks the root drizzle.config.* plus every *.config.* under src/,
database/, and app/, loads each candidate with jiti, and keeps the ones whose default export duck-types
as a drizzle-kit config — a dialect field matching a real drizzle-kit dialect (sqlite, postgresql,
mysql, turso, …). Your config just needs a default export:
export const databaseConfig = new DrizzleLibSQLConfig(schema, relations, {
dialect: "sqlite",
schema: "./src/database/schema.ts",
out: "./migrations",
get url(): string {
return Platform.get("TURSO_DATABASE_URL") ?? "file::memory:?cache=shared";
},
get authToken(): string | undefined {
return Platform.get("TURSO_AUTH_TOKEN");
},
busyTimeoutMs: 5000,
});
export default databaseConfig;
The loaded driver also self-describes how a database applies, so migrate never needs you to name the
dialect: a d1-http driver applies through wrangler d1 migrations apply, not drizzle-kit; a
durable-sqlite driver bundles its migration in-DO with no CLI apply step at all; anything else runs
through plain drizzle-kit migrate.
Pick which database
An app with one detected config just runs against it. An app with several — a flagship monorepo with a
Postgres orders database and a separate analytics database — prompts a select menu in an interactive
terminal:
? Which database? ›
❯ All databases
orders.config.ts
analytics.config.ts
In a non-interactive shell (CI) that same run defaults to all detected configs rather than hanging on a
prompt with no TTY to answer it. Pass --config <path> to target one file directly, bypassing detection
entirely — the command still loads and duck-types it, so an unusual location or filename works too.
Commands
Every subcommand takes --cwd (project root), --env (the .env.<mode> overlay to load — default
development), --env-file (an explicit dotenv file layered over the cascade), and --config (target one
file, skip detection).
| Subcommand | Runs | Notes |
|---|---|---|
generate | drizzle-kit generate | Diffs your schema, writes a new migration folder. --name labels it. |
migrate | drizzle-kit migrate | Applies pending migrations. Skips (with a message) any config whose driver applies outside drizzle-kit. |
status | — | Lists the .sql files already on disk per database — no live query, so it can't tell applied from pending. |
push | drizzle-kit push | Pushes the schema straight to the database, no migration files. Destructive — dev only. |
push is the one command that touches your schema without a reviewable diff, so it refuses to run
unattended: pass --yes to confirm non-interactively, or answer the confirmation prompt in a terminal.
pnpx heximon migrations generate --name add-users-index
pnpx heximon migrations push --yes # dev database only
status can't tell you what's applied. It lists the migration files under your out folder, not what
the live database has run — that needs a query against the database's own bookkeeping table, which this
view doesn't make. For that, inject the MigrationRunner and call
status() from your app instead..env is loaded for you
Before detecting anything, the command loads the dotenv cascade — .env < .env.local <
.env.<mode> < .env.<mode>.local, shell wins — into process.env, so the spawned drizzle-kit child
inherits DATABASE_URL (or whatever your config's getter reads) without you exporting it by hand. An
explicit --env-file layers on top of the cascade rather than replacing it, for a one-off like migrating
against a .env.production snapshot locally.
pnpx heximon migrations migrate --env production
pnpx heximon migrations migrate --env-file .env.production
The same detector drives deploy-time migrations
heximon deploy --migrate (see Deploying a Heximon app)
reuses this exact detection — every drizzle-kit-mode config becomes a target the deploy applies in one
pass, sequenced around the ship.
The one deliberate difference: a deploy never loads your dev .env cascade (a stray dev
DATABASE_URL migrating the wrong database during a production ship would be a bad day), inheriting only
the deploy's own environment plus --env-file.
See also
- Database migrations — the unified config, the injectable
MigrationRunner, and applying migrations on boot. - Deploying a Heximon app — the pre-ship migration gate and
--migrateapply step. - Drizzle ORM — the per-dialect config classes (
DrizzleLibSQLConfig,DrizzlePgConfig,DrizzleMySQL2Config) this command detects.
Create a New App
heximon create (and pnpm create heximon) — scaffold a working Heximon app from a starter template in one command, with the template, validator, git, and install steps prompted or flagged.
Deploying a Heximon app
heximon deploy runs vp build then ships via wrangler deploy, vercel deploy --prebuilt, netlify deploy, or deployctl deploy — with --provision, --create, --migrate, --profile, --verify, and --destroy.