From d827b465cf70c2e141e77f4f7f150ae319ca5716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Sat, 3 Oct 2026 15:17:42 +0000 Subject: [PATCH] Commit the first migration, and apply schema the same way everywhere `drizzle/` was gitignored, so every environment re-derived the schema from src/db/schema and nothing recorded what a given release expected. That is survivable while nothing is deployed and a problem the moment something is. The generated migration is now committed: four tables, three enums, five foreign keys, and the per-farm unique index on gateway_printer_id. It needed one hand-added line to be usable on its own. drizzle-kit does not emit extension statements, but frame_embeddings.embedding is vector(512) with an ivfflat index, so as generated the migration only applied to a database somebody had already run `CREATE EXTENSION vector` on - which was true locally precisely because dev-env.sh does that, and would not have been true on a fresh deploy. `CREATE EXTENSION IF NOT EXISTS vector` now leads the file, and it survives regeneration: drizzle only appends new migrations, and the diffing snapshot lives in meta/0000_snapshot.json rather than in the SQL. Verified by dropping the database, recreating it with no extensions at all (`pg_extension` count 0), and running db:migrate: the extension and all four tables were created, `db:generate` then reported no pending changes, and the e2e script still passed against both live printers on the result. `dev-env.sh schema` now runs `migrate` instead of `push`, so a local database and a deployed one get the schema by the same route - testing `push` locally while production runs `migrate` tests two different things. `push` is still available as its own command for iterating on a table that is still moving, with a comment about what it does not record. --- .gitignore | 1 - README.md | 17 +- drizzle/0000_watery_firedrake.sql | 126 +++++++ drizzle/meta/0000_snapshot.json | 544 ++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 13 + scripts/dev-env.sh | 25 +- 6 files changed, 715 insertions(+), 11 deletions(-) create mode 100644 drizzle/0000_watery_firedrake.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json diff --git a/.gitignore b/.gitignore index 6ab3a8d..c12c3f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,5 @@ bun.lockb bun.lock .env .env.local -drizzle/ *.log .DS_Store diff --git a/README.md b/README.md index e4b5ec8..e0ffc8b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ with pgvector for frame embeddings), Valkey Streams, and Cloudflare R2 storage. ```sh bun install scripts/dev-env.sh up # Valkey + Postgres/pgvector, and print the URLs -scripts/dev-env.sh schema # or: bun run db:migrate +scripts/dev-env.sh schema # apply drizzle/ - same as bun run db:migrate cp .env.example .env # fill in the URLs it printed, plus R2_*, SUPABASE_JWT_SECRET bun run dev ``` @@ -20,6 +20,16 @@ dockerd needs `CAP_NET_ADMIN` for its bridge and user namespaces to unpack an image, and neither can be granted from inside. `scripts/dev-env.sh status` says which backend is in use. +Schema changes go through migrations: edit `src/db/schema/`, run +`bun run db:generate`, and commit the file it writes to `drizzle/` along with +the schema change. `scripts/dev-env.sh push` reshapes a local database straight +from the schema with no migration file, which is faster while a table is still +moving but records nothing and will drop a column to make things match - so +generate before committing. `drizzle/0000_*.sql` creates the `vector` extension +itself; that line is hand-added, since drizzle-kit does not emit extension +statements and the migration would otherwise only apply to a database somebody +had already prepared. + ## Test ```sh @@ -61,8 +71,9 @@ Valkey and Postgres+pgvector for local development. (`"p1p"`) onto rows here; telemetry for an unclaimed id is logged and skipped, never inserted. -`frame_embeddings.embedding` requires the `vector` extension: -`CREATE EXTENSION IF NOT EXISTS vector;` (`scripts/dev-env.sh up` does this). +`frame_embeddings.embedding` requires the `vector` extension. The first +migration creates it, and `scripts/dev-env.sh up` also does so for the `push` +path, which does not run migrations. ## Dependency pins diff --git a/drizzle/0000_watery_firedrake.sql b/drizzle/0000_watery_firedrake.sql new file mode 100644 index 0000000..eab120e --- /dev/null +++ b/drizzle/0000_watery_firedrake.sql @@ -0,0 +1,126 @@ +-- Hand-added to the generated migration: drizzle-kit does not emit extension +-- statements, but frame_embeddings.embedding is vector(512) and its index is +-- ivfflat, so this migration cannot apply to a database without pgvector. A +-- committed migration has to stand on its own - relying on whoever set the +-- database up having run this first is how a deploy fails. +-- +-- Safe to keep across regeneration: drizzle only ever appends new files, and +-- the diffing snapshot lives in meta/0000_snapshot.json, not here. +CREATE EXTENSION IF NOT EXISTS vector; +--> statement-breakpoint +DO $$ BEGIN + CREATE TYPE "public"."printer_status" AS ENUM('offline', 'idle', 'printing', 'paused', 'error', 'maintenance'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + CREATE TYPE "public"."printer_vendor" AS ENUM('bambu', 'prusa', 'klipper'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + CREATE TYPE "public"."print_job_status" AS ENUM('queued', 'uploading', 'printing', 'paused', 'completed', 'failed', 'cancelled'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "farms" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "owner_id" uuid NOT NULL, + "edge_proxy_token" text NOT NULL, + "location" text, + "settings" jsonb DEFAULT '{}'::jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "printers" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "farm_id" uuid NOT NULL, + "name" text NOT NULL, + "vendor" "printer_vendor" NOT NULL, + "status" "printer_status" DEFAULT 'offline' NOT NULL, + "lan_address" text, + "serial_number" text, + "access_code" text, + "gateway_printer_id" text, + "nozzle_temp_c" real, + "nozzle_target_temp_c" real, + "bed_temp_c" real, + "bed_target_temp_c" real, + "progress_percent" integer, + "current_layer" integer, + "total_layers" integer, + "remaining_minutes" integer, + "current_job_name" text, + "active_alerts" integer, + "vendor_state" text, + "vendor_error_code" integer, + "metadata" jsonb DEFAULT '{}'::jsonb, + "last_seen_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "print_jobs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "farm_id" uuid NOT NULL, + "printer_id" uuid, + "file_name" text NOT NULL, + "r2_object_key" text NOT NULL, + "status" "print_job_status" DEFAULT 'queued' NOT NULL, + "progress_pct" real DEFAULT 0 NOT NULL, + "estimated_time_s" real, + "elapsed_time_s" real, + "submitted_by" uuid NOT NULL, + "metadata" jsonb DEFAULT '{}'::jsonb, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "frame_embeddings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "print_job_id" uuid NOT NULL, + "printer_id" uuid NOT NULL, + "r2_object_key" text NOT NULL, + "embedding" vector(512) NOT NULL, + "captured_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "printers" ADD CONSTRAINT "printers_farm_id_farms_id_fk" FOREIGN KEY ("farm_id") REFERENCES "public"."farms"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "print_jobs" ADD CONSTRAINT "print_jobs_farm_id_farms_id_fk" FOREIGN KEY ("farm_id") REFERENCES "public"."farms"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "print_jobs" ADD CONSTRAINT "print_jobs_printer_id_printers_id_fk" FOREIGN KEY ("printer_id") REFERENCES "public"."printers"("id") ON DELETE set null ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "frame_embeddings" ADD CONSTRAINT "frame_embeddings_print_job_id_print_jobs_id_fk" FOREIGN KEY ("print_job_id") REFERENCES "public"."print_jobs"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "frame_embeddings" ADD CONSTRAINT "frame_embeddings_printer_id_printers_id_fk" FOREIGN KEY ("printer_id") REFERENCES "public"."printers"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "printers_farm_gateway_printer_id_idx" ON "printers" USING btree ("farm_id","gateway_printer_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "frame_embeddings_embedding_ivfflat_idx" ON "frame_embeddings" USING ivfflat ("embedding" vector_cosine_ops) WITH (lists=100); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..a8e36b9 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,544 @@ +{ + "id": "f60fed84-9b69-403f-a789-9e29cee970f1", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.farms": { + "name": "farms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "edge_proxy_token": { + "name": "edge_proxy_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.printers": { + "name": "printers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "farm_id": { + "name": "farm_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vendor": { + "name": "vendor", + "type": "printer_vendor", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "printer_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'offline'" + }, + "lan_address": { + "name": "lan_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serial_number": { + "name": "serial_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_code": { + "name": "access_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gateway_printer_id": { + "name": "gateway_printer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nozzle_temp_c": { + "name": "nozzle_temp_c", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "nozzle_target_temp_c": { + "name": "nozzle_target_temp_c", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "bed_temp_c": { + "name": "bed_temp_c", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "bed_target_temp_c": { + "name": "bed_target_temp_c", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "current_layer": { + "name": "current_layer", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_layers": { + "name": "total_layers", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining_minutes": { + "name": "remaining_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "current_job_name": { + "name": "current_job_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_alerts": { + "name": "active_alerts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vendor_state": { + "name": "vendor_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_error_code": { + "name": "vendor_error_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "printers_farm_gateway_printer_id_idx": { + "name": "printers_farm_gateway_printer_id_idx", + "columns": [ + { + "expression": "farm_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_printer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "printers_farm_id_farms_id_fk": { + "name": "printers_farm_id_farms_id_fk", + "tableFrom": "printers", + "tableTo": "farms", + "columnsFrom": [ + "farm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.print_jobs": { + "name": "print_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "farm_id": { + "name": "farm_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "printer_id": { + "name": "printer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "r2_object_key": { + "name": "r2_object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "print_job_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "progress_pct": { + "name": "progress_pct", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "estimated_time_s": { + "name": "estimated_time_s", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "elapsed_time_s": { + "name": "elapsed_time_s", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "submitted_by": { + "name": "submitted_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "print_jobs_farm_id_farms_id_fk": { + "name": "print_jobs_farm_id_farms_id_fk", + "tableFrom": "print_jobs", + "tableTo": "farms", + "columnsFrom": [ + "farm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "print_jobs_printer_id_printers_id_fk": { + "name": "print_jobs_printer_id_printers_id_fk", + "tableFrom": "print_jobs", + "tableTo": "printers", + "columnsFrom": [ + "printer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.frame_embeddings": { + "name": "frame_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "print_job_id": { + "name": "print_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "printer_id": { + "name": "printer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "r2_object_key": { + "name": "r2_object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(512)", + "primaryKey": false, + "notNull": true + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "frame_embeddings_embedding_ivfflat_idx": { + "name": "frame_embeddings_embedding_ivfflat_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "ivfflat", + "with": { + "lists": 100 + } + } + }, + "foreignKeys": { + "frame_embeddings_print_job_id_print_jobs_id_fk": { + "name": "frame_embeddings_print_job_id_print_jobs_id_fk", + "tableFrom": "frame_embeddings", + "tableTo": "print_jobs", + "columnsFrom": [ + "print_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "frame_embeddings_printer_id_printers_id_fk": { + "name": "frame_embeddings_printer_id_printers_id_fk", + "tableFrom": "frame_embeddings", + "tableTo": "printers", + "columnsFrom": [ + "printer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.printer_status": { + "name": "printer_status", + "schema": "public", + "values": [ + "offline", + "idle", + "printing", + "paused", + "error", + "maintenance" + ] + }, + "public.printer_vendor": { + "name": "printer_vendor", + "schema": "public", + "values": [ + "bambu", + "prusa", + "klipper" + ] + }, + "public.print_job_status": { + "name": "print_job_status", + "schema": "public", + "values": [ + "queued", + "uploading", + "printing", + "paused", + "completed", + "failed", + "cancelled" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..9a8b390 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1791029521420, + "tag": "0000_watery_firedrake", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/scripts/dev-env.sh b/scripts/dev-env.sh index 9e50df7..fe1e4f6 100755 --- a/scripts/dev-env.sh +++ b/scripts/dev-env.sh @@ -24,7 +24,8 @@ # scripts/dev-env.sh reset stop them AND delete all data # scripts/dev-env.sh status what is running, and which backend # scripts/dev-env.sh psql open a shell on the database -# scripts/dev-env.sh schema apply the Drizzle schema (drizzle-kit push) +# scripts/dev-env.sh schema apply the committed migrations (drizzle-kit migrate) +# scripts/dev-env.sh push reshape the db from src/db/schema, no migration # scripts/dev-env.sh env print the DATABASE_URL/VALKEY_URL to use set -euo pipefail @@ -204,12 +205,21 @@ cmd_status() { case "$(backend)" in docker) echo "backend: docker"; sudo docker cmd_psql() { PGPASSWORD=continuum psql -h localhost -p "$PG_PORT" -U continuum -d continuum "$@"; } cmd_env() { echo "DATABASE_URL=$DEV_DATABASE_URL"; echo "VALKEY_URL=$DEV_VALKEY_URL"; } +# Applies the committed migrations in drizzle/, the same way a deploy would. +# This is the default because it is the path that has to work in production - +# testing `push` locally and `migrate` in production tests two different things. cmd_schema() { - # `push` rather than generate+migrate: no migrations are committed and the - # schema is still moving. Switch once it settles - push diffs the live - # database against the schema and will happily drop a column, which is fine - # locally and nowhere else. - echo "==> applying the Drizzle schema to $DEV_DATABASE_URL" + echo "==> applying committed migrations to $DEV_DATABASE_URL" + DATABASE_URL="$DEV_DATABASE_URL" bunx drizzle-kit migrate +} + +# Diffs the live database against src/db/schema and reshapes it in place, with +# no migration file. Faster while iterating on the schema, and destructive: it +# will drop a column to make the database match. Local only - and whatever it +# does is not recorded anywhere, so run `bun run db:generate` before committing +# a schema change made this way. +cmd_push() { + echo "==> pushing src/db/schema straight onto $DEV_DATABASE_URL" DATABASE_URL="$DEV_DATABASE_URL" bunx drizzle-kit push --force } @@ -220,6 +230,7 @@ case "${1:-}" in status) cmd_status ;; psql) shift; cmd_psql "$@" ;; schema) cmd_schema ;; + push) cmd_push ;; env) cmd_env ;; - *) sed -n '2,30p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'; exit 1 ;; + *) awk 'NR>1 && /^#/ { sub(/^# ?/, ""); print; next } NR>1 { exit }' "${BASH_SOURCE[0]}"; exit 1 ;; esac