From b6789a8db3e2da160157dbc720542749dadfa557 Mon Sep 17 00:00:00 2001 From: Gregory Boch Date: Wed, 28 Oct 2026 17:09:50 +0100 Subject: [PATCH 1/5] feat(create-db): hand the claim URL on in every output mode A database from create-db is deleted unless someone claims it, but the non-interactive modes made the claim URL easy to lose: --quiet never printed it, --env only wrote it to the file, and --json left it to the agent to notice. - --json adds a nextStep field telling the agent to give the user the claim URL before the deletion time. - --quiet prints the claim URL to stderr; stdout is still only the connection string. - --env prints the claim URL and deletion time after writing the file. - The claim URL carries the output mode as utm_content, so claim page visits can be split by mode. Co-Authored-By: Claude Opus 5.5 --- .changeset/claim-link-handoff.md | 5 ++ README.md | 2 +- packages/create-db/README.md | 9 ++- packages/create-db/__tests__/output.test.ts | 66 ++++++++++++++++++- packages/create-db/src/cli/commands/create.ts | 29 +++++--- packages/create-db/src/cli/output.ts | 49 ++++++++++++++ 6 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 .changeset/claim-link-handoff.md diff --git a/.changeset/claim-link-handoff.md b/.changeset/claim-link-handoff.md new file mode 100644 index 0000000..6e5949d --- /dev/null +++ b/.changeset/claim-link-handoff.md @@ -0,0 +1,5 @@ +--- +"create-db": minor +--- + +Hand the claim URL to whoever needs it in every output mode. `--json` adds a `nextStep` field telling the agent or script to give the user the claim URL before the deletion time. `--quiet` prints the claim URL to stderr, and `--env` prints it after writing the file. The claim URL now carries the output mode as `utm_content`. diff --git a/README.md b/README.md index 28827d9..85f2201 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This monorepo contains tools and services that enable developers to quickly prov | `--env`, `-e` | Write `DATABASE_URL` and `CLAIM_URL` to the specified file | `--env .env` | | `--ttl`, `-t` | Set auto-delete TTL (`30m`, `1h` ... `24h`) | `--ttl 5h` | | `--copy`, `-c` | Copy connection string to clipboard | `--copy` | -| `--quiet`, `-q` | Output only the connection string | `--quiet` | +| `--quiet`, `-q` | Output only the connection string (claim URL on stderr) | `--quiet` | | `--open`, `-o` | Open claim URL in browser | `--open` | | `--help`, `-h` | Show help information | `--help` | diff --git a/packages/create-db/README.md b/packages/create-db/README.md index 7477835..cc72782 100644 --- a/packages/create-db/README.md +++ b/packages/create-db/README.md @@ -40,7 +40,7 @@ npx create-db regions # List available regions | `--env ` | `-e` | Write DATABASE_URL and CLAIM_URL to specified .env file | | `--ttl ` | `-t` | Custom database TTL (`30m`, `1h` ... `24h`) | | `--copy` | `-c` | Copy connection string to clipboard | -| `--quiet` | `-q` | Output only the connection string | +| `--quiet` | `-q` | Output only the connection string (the claim URL goes to stderr) | | `--open` | `-o` | Open claim URL in browser | | `--help` | `-h` | Show help message | | `--version` | | Show version | @@ -116,10 +116,13 @@ When using `--json` with `create`, the output includes: "deletionDate": "2025-12-13T12:00:00.000Z", "region": "us-east-1", "name": "2025-12-12T12:00:00.000Z", - "projectId": "proj_..." + "projectId": "proj_...", + "nextStep": "The database is deleted at 2025-12-13T12:00:00.000Z unless the user claims it, which keeps it for free. Give the user this claim URL: https://create-db.prisma.io/claim?projectID=..." } ``` +`nextStep` is for agents and scripts that run `create-db` for someone else: the database is deleted at `deletionDate` unless that person opens `claimUrl` and claims it. + When using `--json` with `regions`, the output is an array of region objects: ```json @@ -141,6 +144,8 @@ DATABASE_URL="postgresql://user:pass@host:5432/postgres?sslmode=require" CLAIM_URL="https://create-db.prisma.io/claim?projectID=..." ``` +The CLI also prints the claim URL and deletion time, so the person running it can claim the database before it is deleted. + ## Programmatic API You can also use `create-db` as a library in your Node.js applications: diff --git a/packages/create-db/__tests__/output.test.ts b/packages/create-db/__tests__/output.test.ts index b03f3a1..5e9b68b 100644 --- a/packages/create-db/__tests__/output.test.ts +++ b/packages/create-db/__tests__/output.test.ts @@ -2,7 +2,13 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import fs from "fs"; import os from "os"; import path from "path"; -import { writeEnvFile } from "../src/cli/output.js"; +import { + writeEnvFile, + withOutputMode, + formatJsonResult, + printClaimReminder, +} from "../src/cli/output.js"; +import type { DatabaseResult } from "../src/types.js"; describe("writeEnvFile", () => { let tempDir: string; @@ -142,3 +148,61 @@ describe("writeEnvFile", () => { expect(content).toContain(`DATABASE_URL="${connectionString}"`); }); }); + +const sampleResult: DatabaseResult = { + success: true, + connectionString: "postgresql://user:pass@host:5432/postgres", + claimUrl: + "https://create-db.prisma.io/claim?projectID=proj_123&utm_source=create-db&utm_medium=cli", + deletionDate: "2026-10-29T12:00:00.000Z", + region: "us-east-1", + name: "2026-10-28T12:00:00.000Z", + projectId: "proj_123", +}; + +describe("withOutputMode", () => { + it("appends the output mode to the claim URL as utm_content", () => { + const tagged = withOutputMode(sampleResult, "json"); + expect(tagged.claimUrl).toBe(`${sampleResult.claimUrl}&utm_content=json`); + expect(new URL(tagged.claimUrl).searchParams.get("projectID")).toBe("proj_123"); + }); + + it("leaves the input result unchanged", () => { + withOutputMode(sampleResult, "quiet"); + expect(sampleResult.claimUrl).not.toContain("utm_content"); + }); +}); + +describe("formatJsonResult", () => { + it("keeps every result field and adds a nextStep with the claim URL and deletion time", () => { + const json = formatJsonResult(sampleResult); + expect(json).toMatchObject(sampleResult); + expect(json.nextStep).toContain(sampleResult.claimUrl); + expect(json.nextStep).toContain(sampleResult.deletionDate); + expect(json.nextStep.endsWith(sampleResult.claimUrl)).toBe(true); + }); +}); + +describe("printClaimReminder", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("prints the claim URL to stdout by default", () => { + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + printClaimReminder(sampleResult); + expect(log).toHaveBeenCalledTimes(1); + expect(log.mock.calls[0][0]).toContain(sampleResult.claimUrl); + expect(error).not.toHaveBeenCalled(); + }); + + it("prints to stderr when asked, keeping stdout clean", () => { + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + printClaimReminder(sampleResult, "stderr"); + expect(error).toHaveBeenCalledTimes(1); + expect(error.mock.calls[0][0]).toContain(sampleResult.claimUrl); + expect(log).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/create-db/src/cli/commands/create.ts b/packages/create-db/src/cli/commands/create.ts index e157fa8..cbfdf3b 100644 --- a/packages/create-db/src/cli/commands/create.ts +++ b/packages/create-db/src/cli/commands/create.ts @@ -23,6 +23,9 @@ import { printJson, printError, printSuccess, + printClaimReminder, + formatJsonResult, + withOutputMode, writeEnvFile, copyToClipboard, openUrlInBrowser, @@ -141,7 +144,7 @@ export async function handleCreate(input: CreateFlagsInput): Promise { } await ensureOnline(); - const result = await createDatabase( + const created = await createDatabase( region, userAgent, cliRunId, @@ -151,18 +154,24 @@ export async function handleCreate(input: CreateFlagsInput): Promise { await flushAnalytics(); if (input.json) { - printJson(result); - if (!result.success) { + printJson( + created.success + ? formatJsonResult(withOutputMode(created, "json")) + : created + ); + if (!created.success) { process.exit(1); } return; } - if (!result.success) { - printError(result.message); + if (!created.success) { + printError(created.message); process.exit(1); } + const result = withOutputMode(created, input.quiet ? "quiet" : "env"); + if (envEnabled) { const writeResult = writeEnvFile(envPath!, result.connectionString, result.claimUrl); if (!writeResult.success) { @@ -172,11 +181,13 @@ export async function handleCreate(input: CreateFlagsInput): Promise { if (!input.quiet) { printSuccess(`Wrote DATABASE_URL and CLAIM_URL to ${envPath}`); + printClaimReminder(result); } } if (input.quiet) { console.log(result.connectionString ?? ""); + printClaimReminder(result, "stderr"); } if (input.copy) { @@ -228,7 +239,7 @@ export async function handleCreate(input: CreateFlagsInput): Promise { const spinner = createSpinner(); spinner.start(region); - const result = await createDatabase( + const created = await createDatabase( region, userAgent, cliRunId, @@ -236,12 +247,14 @@ export async function handleCreate(input: CreateFlagsInput): Promise { ttlMs ); - if (!result.success) { - spinner.error(result.message); + if (!created.success) { + spinner.error(created.message); await flushAnalytics(); process.exit(1); } + const result = withOutputMode(created, "default"); + spinner.success(); printDatabaseResult(result); diff --git a/packages/create-db/src/cli/output.ts b/packages/create-db/src/cli/output.ts index 6cd9313..64df811 100644 --- a/packages/create-db/src/cli/output.ts +++ b/packages/create-db/src/cli/output.ts @@ -69,6 +69,55 @@ export function printDatabaseResult(result: DatabaseResult) { ); } +/** How the CLI printed its result; recorded on the claim URL as `utm_content`. */ +export type OutputMode = "default" | "json" | "env" | "quiet"; + +/** + * Tag the claim URL with the output mode, so claim page visits can be + * attributed to the mode that produced them. + * @param result - The successful database creation result + * @param mode - The output mode in use + * @returns The result with a tagged claim URL + */ +export function withOutputMode( + result: DatabaseResult, + mode: OutputMode +): DatabaseResult { + return { ...result, claimUrl: `${result.claimUrl}&utm_content=${mode}` }; +} + +/** + * Build the JSON printed by `--json`: the result plus a `nextStep` telling the + * agent or script to pass the claim URL on, since the database is deleted + * unless someone claims it. + * @param result - The successful database creation result + * @returns The JSON payload + */ +export function formatJsonResult(result: DatabaseResult) { + return { + ...result, + nextStep: `The database is deleted at ${result.deletionDate} unless the user claims it, which keeps it for free. Give the user this claim URL: ${result.claimUrl}`, + }; +} + +/** + * Print a one-line claim reminder for the non-interactive modes. + * @param result - The successful database creation result + * @param stream - Where to print; `stderr` keeps stdout clean for `--quiet` + */ +export function printClaimReminder( + result: DatabaseResult, + stream: "stdout" | "stderr" = "stdout" +) { + const expiryFormatted = new Date(result.deletionDate).toLocaleString(); + const line = `Keep your database for free: ${result.claimUrl} (deleted on ${expiryFormatted} if not claimed)`; + if (stream === "stderr") { + console.error(line); + } else { + console.log(line); + } +} + /** * Print data as formatted JSON. * @param data - Data to serialize and print From c9b2beca2d0228fe8e10fabacee06a40a00a84f6 Mon Sep 17 00:00:00 2001 From: Gregory Boch Personal Date: Thu, 29 Oct 2026 09:59:13 +0100 Subject: [PATCH 2/5] chore(release): bump create-db, create-pg, create-postgres to 1.3.0 The release job publishes the version in package.json. main still says 1.2.0 while npm has 1.2.1, so every publish since March has failed. Bump the version here instead of adding a changeset. Also start a query string when the claim URL has none, so the output-mode tag doesn't depend on the claim URL's current shape. Co-Authored-By: Claude Opus 5.5 --- .changeset/claim-link-handoff.md | 5 ----- packages/create-db/__tests__/output.test.ts | 15 +++++++++++++++ packages/create-db/package.json | 2 +- packages/create-db/src/cli/output.ts | 9 +++++++-- packages/create-pg/package.json | 2 +- packages/create-postgres/package.json | 2 +- 6 files changed, 25 insertions(+), 10 deletions(-) delete mode 100644 .changeset/claim-link-handoff.md diff --git a/.changeset/claim-link-handoff.md b/.changeset/claim-link-handoff.md deleted file mode 100644 index 6e5949d..0000000 --- a/.changeset/claim-link-handoff.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-db": minor ---- - -Hand the claim URL to whoever needs it in every output mode. `--json` adds a `nextStep` field telling the agent or script to give the user the claim URL before the deletion time. `--quiet` prints the claim URL to stderr, and `--env` prints it after writing the file. The claim URL now carries the output mode as `utm_content`. diff --git a/packages/create-db/__tests__/output.test.ts b/packages/create-db/__tests__/output.test.ts index 5e9b68b..f98790a 100644 --- a/packages/create-db/__tests__/output.test.ts +++ b/packages/create-db/__tests__/output.test.ts @@ -167,6 +167,21 @@ describe("withOutputMode", () => { expect(new URL(tagged.claimUrl).searchParams.get("projectID")).toBe("proj_123"); }); + it("starts a query string when the claim URL has none", () => { + const tagged = withOutputMode( + { ...sampleResult, claimUrl: "https://create-db.prisma.io/claim/proj_123" }, + "env" + ); + expect(tagged.claimUrl).toBe("https://create-db.prisma.io/claim/proj_123?utm_content=env"); + }); + + it("keeps the encoding of existing params", () => { + const claimUrl = + "https://create-db.prisma.io/claim?projectID=proj_123&utm_source=claude-code/my-app&utm_medium=cli"; + const tagged = withOutputMode({ ...sampleResult, claimUrl }, "default"); + expect(tagged.claimUrl).toBe(`${claimUrl}&utm_content=default`); + }); + it("leaves the input result unchanged", () => { withOutputMode(sampleResult, "quiet"); expect(sampleResult.claimUrl).not.toContain("utm_content"); diff --git a/packages/create-db/package.json b/packages/create-db/package.json index 26f715d..baf386a 100644 --- a/packages/create-db/package.json +++ b/packages/create-db/package.json @@ -1,6 +1,6 @@ { "name": "create-db", - "version": "1.2.0", + "version": "1.3.0", "description": "Instantly create a temporary Prisma Postgres database with one command, then claim and persist it in your Prisma Data Platform project when ready.", "type": "module", "exports": { diff --git a/packages/create-db/src/cli/output.ts b/packages/create-db/src/cli/output.ts index 64df811..3f2d2ed 100644 --- a/packages/create-db/src/cli/output.ts +++ b/packages/create-db/src/cli/output.ts @@ -74,7 +74,8 @@ export type OutputMode = "default" | "json" | "env" | "quiet"; /** * Tag the claim URL with the output mode, so claim page visits can be - * attributed to the mode that produced them. + * attributed to the mode that produced them. The existing query string is + * left as is rather than re-serialized, so other params keep their encoding. * @param result - The successful database creation result * @param mode - The output mode in use * @returns The result with a tagged claim URL @@ -83,7 +84,11 @@ export function withOutputMode( result: DatabaseResult, mode: OutputMode ): DatabaseResult { - return { ...result, claimUrl: `${result.claimUrl}&utm_content=${mode}` }; + const separator = result.claimUrl.includes("?") ? "&" : "?"; + return { + ...result, + claimUrl: `${result.claimUrl}${separator}utm_content=${mode}`, + }; } /** diff --git a/packages/create-pg/package.json b/packages/create-pg/package.json index e676fba..8b3f123 100644 --- a/packages/create-pg/package.json +++ b/packages/create-pg/package.json @@ -1,6 +1,6 @@ { "name": "create-pg", - "version": "1.2.0", + "version": "1.3.0", "description": "Instantly create a temporary Prisma Postgres database with one command, then claim and persist it in your Prisma Data Platform project when ready.", "author": "prisma", "repository": { diff --git a/packages/create-postgres/package.json b/packages/create-postgres/package.json index 8849371..e6a6909 100644 --- a/packages/create-postgres/package.json +++ b/packages/create-postgres/package.json @@ -1,6 +1,6 @@ { "name": "create-postgres", - "version": "1.2.0", + "version": "1.3.0", "description": "Instantly create a temporary Prisma Postgres database with one command, then claim and persist it in your Prisma Data Platform project when ready.", "author": "prisma", "repository": { From 9a9d57a238895fc234b00eaf99b922c71bb2f93c Mon Sep 17 00:00:00 2001 From: Gregory Boch Personal Date: Thu, 29 Oct 2026 12:52:51 +0100 Subject: [PATCH 3/5] fix(create-db): write the env file with --json, show the time zone on expiry `--json --env ` returned before writing the env file, so agents asking for both got JSON and no .env. The JSON branch now writes the file first, still prints the JSON if the write fails, and exits with code 1 in that case. The deletion time printed by the claim reminder and the default output had no time zone, so a time printed in a UTC sandbox and relayed to a user elsewhere was off by their offset. Both now use one formatter that includes the zone. Co-Authored-By: Claude Opus 5.5 --- packages/create-db/README.md | 2 ++ packages/create-db/__tests__/create.test.ts | 20 +++++++++++++++++++ packages/create-db/__tests__/output.test.ts | 12 +++++++++++ packages/create-db/src/cli/commands/create.ts | 20 ++++++++++++++----- packages/create-db/src/cli/output.ts | 16 +++++++++++++-- 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/packages/create-db/README.md b/packages/create-db/README.md index cc72782..24fcc53 100644 --- a/packages/create-db/README.md +++ b/packages/create-db/README.md @@ -123,6 +123,8 @@ When using `--json` with `create`, the output includes: `nextStep` is for agents and scripts that run `create-db` for someone else: the database is deleted at `deletionDate` unless that person opens `claimUrl` and claims it. +Add `--env ` to also write `DATABASE_URL` and `CLAIM_URL` to that file. If the file can't be written, the JSON is still printed and the command exits with code 1. + When using `--json` with `regions`, the output is an array of region objects: ```json diff --git a/packages/create-db/__tests__/create.test.ts b/packages/create-db/__tests__/create.test.ts index 0bb113e..02a3827 100644 --- a/packages/create-db/__tests__/create.test.ts +++ b/packages/create-db/__tests__/create.test.ts @@ -2,6 +2,8 @@ import { describe, it, expect } from "vitest"; import { execa } from "execa"; import { fileURLToPath } from "url"; import path from "path"; +import fs from "fs"; +import os from "os"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CLI_PATH = path.resolve(__dirname, "../dist/cli.mjs"); @@ -62,6 +64,24 @@ describe("CLI database creation", () => { expect(parsed.success).toBe(true); }, 30000); + it("writes the env file and prints JSON with --json --env", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "create-db-json-env-")); + const envPath = path.join(dir, ".env"); + try { + const result = await runCli(["--json", "--env", envPath]); + expect(result.exitCode).toBe(0); + const parsed = JSON.parse(result.stdout.trim()); + expect(parsed.success).toBe(true); + expect(parsed.nextStep).toContain(parsed.claimUrl); + + const content = fs.readFileSync(envPath, "utf8"); + expect(content).toContain(`CLAIM_URL="${parsed.claimUrl}"`); + expect(content).toContain("DATABASE_URL="); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }, 30000); + it("lists regions with regions command", async () => { const result = await runCli(["regions"]); if (result.exitCode !== 0) { diff --git a/packages/create-db/__tests__/output.test.ts b/packages/create-db/__tests__/output.test.ts index f98790a..3862030 100644 --- a/packages/create-db/__tests__/output.test.ts +++ b/packages/create-db/__tests__/output.test.ts @@ -5,6 +5,7 @@ import path from "path"; import { writeEnvFile, withOutputMode, + formatDeletionDate, formatJsonResult, printClaimReminder, } from "../src/cli/output.js"; @@ -188,6 +189,17 @@ describe("withOutputMode", () => { }); }); +describe("formatDeletionDate", () => { + it("includes the time zone", () => { + const date = new Date(sampleResult.deletionDate); + const zone = new Intl.DateTimeFormat(undefined, { timeZoneName: "short" }) + .formatToParts(date) + .find((part) => part.type === "timeZoneName")?.value; + expect(zone).toBeTruthy(); + expect(formatDeletionDate(sampleResult.deletionDate)).toContain(zone); + }); +}); + describe("formatJsonResult", () => { it("keeps every result field and adds a nextStep with the claim URL and deletion time", () => { const json = formatJsonResult(sampleResult); diff --git a/packages/create-db/src/cli/commands/create.ts b/packages/create-db/src/cli/commands/create.ts index cbfdf3b..7b441b6 100644 --- a/packages/create-db/src/cli/commands/create.ts +++ b/packages/create-db/src/cli/commands/create.ts @@ -154,12 +154,22 @@ export async function handleCreate(input: CreateFlagsInput): Promise { await flushAnalytics(); if (input.json) { - printJson( - created.success - ? formatJsonResult(withOutputMode(created, "json")) - : created - ); if (!created.success) { + printJson(created); + process.exit(1); + } + + const result = withOutputMode(created, "json"); + const writeResult = envEnabled + ? writeEnvFile(envPath!, result.connectionString, result.claimUrl) + : null; + + // Print the JSON even if the env write failed, so the connection + // string and claim URL aren't lost. + printJson(formatJsonResult(result)); + + if (writeResult && !writeResult.success) { + printError(`Failed to write environment variables to ${envPath}: ${writeResult.error}`); process.exit(1); } return; diff --git a/packages/create-db/src/cli/output.ts b/packages/create-db/src/cli/output.ts index 3f2d2ed..d7487dc 100644 --- a/packages/create-db/src/cli/output.ts +++ b/packages/create-db/src/cli/output.ts @@ -36,12 +36,24 @@ export function createSpinner() { }; } +/** + * Format the deletion date in local time with the time zone, so the time + * stays unambiguous when it's relayed from a sandbox in another zone. + * @param deletionDate - ISO deletion date from the database result + * @returns The formatted date, e.g. "10/30/2026, 9:02:10 AM UTC" + */ +export function formatDeletionDate(deletionDate: string) { + return new Date(deletionDate).toLocaleString(undefined, { + timeZoneName: "short", + }); +} + /** * Print formatted database creation result to the console. * @param result - The successful database creation result */ export function printDatabaseResult(result: DatabaseResult) { - const expiryFormatted = new Date(result.deletionDate).toLocaleString(); + const expiryFormatted = formatDeletionDate(result.deletionDate); const clickableUrl = terminalLink(result.claimUrl, result.claimUrl, { fallback: false, }); @@ -114,7 +126,7 @@ export function printClaimReminder( result: DatabaseResult, stream: "stdout" | "stderr" = "stdout" ) { - const expiryFormatted = new Date(result.deletionDate).toLocaleString(); + const expiryFormatted = formatDeletionDate(result.deletionDate); const line = `Keep your database for free: ${result.claimUrl} (deleted on ${expiryFormatted} if not claimed)`; if (stream === "stderr") { console.error(line); From 77712d1f42fb3d7a5c23551346cb8aa08e0fc9a4 Mon Sep 17 00:00:00 2001 From: Gregory Boch Personal Date: Thu, 29 Oct 2026 13:00:29 +0100 Subject: [PATCH 4/5] fix(create-db): flush stdout before exiting on --json failures process.exit() right after printing can cut off output written to a pipe on macOS. Wait for stdout and stderr to flush, then exit with code 1. Setting process.exitCode and returning isn't enough here: trpc-cli calls process.exit(0) after the handler returns, which would hide the failure. Co-Authored-By: Claude Opus 5.5 --- packages/create-db/src/cli/commands/create.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/create-db/src/cli/commands/create.ts b/packages/create-db/src/cli/commands/create.ts index 7b441b6..f20c2ee 100644 --- a/packages/create-db/src/cli/commands/create.ts +++ b/packages/create-db/src/cli/commands/create.ts @@ -60,6 +60,20 @@ function applyOpenFlag(result: DatabaseResult, quiet: boolean) { } } +/** + * Exit once stdout and stderr have flushed, so JSON written to a pipe isn't + * cut off. trpc-cli exits with 0 after the handler returns, so setting + * `process.exitCode` and returning would lose the failure code. + */ +async function exitWhenFlushed(code: number): Promise { + await Promise.all( + [process.stdout, process.stderr].map( + (stream) => new Promise((resolve) => stream.write("", () => resolve())) + ) + ); + process.exit(code); +} + export async function handleCreate(input: CreateFlagsInput): Promise { const cliRunId = randomUUID(); const CLI_NAME = getCommandName(); @@ -156,7 +170,7 @@ export async function handleCreate(input: CreateFlagsInput): Promise { if (input.json) { if (!created.success) { printJson(created); - process.exit(1); + return exitWhenFlushed(1); } const result = withOutputMode(created, "json"); @@ -170,7 +184,7 @@ export async function handleCreate(input: CreateFlagsInput): Promise { if (writeResult && !writeResult.success) { printError(`Failed to write environment variables to ${envPath}: ${writeResult.error}`); - process.exit(1); + return exitWhenFlushed(1); } return; } From 901b0922e669e1b8905789903a7ca9fbf979c76c Mon Sep 17 00:00:00 2001 From: Gregory Boch Personal Date: Thu, 29 Oct 2026 13:26:31 +0100 Subject: [PATCH 5/5] fix(create-db): read --version from package.json `--version` printed a hard-coded 1.1.0, so 1.2.0 and 1.2.1 both reported 1.1.0. Import the version from package.json instead; the bundle inlines only the version string. The version tests now check the exact version. Co-Authored-By: Claude Opus 5.5 --- packages/create-db/__tests__/cli.test.ts | 8 ++++++-- packages/create-db/src/index.ts | 3 ++- packages/create-db/tsconfig.json | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/create-db/__tests__/cli.test.ts b/packages/create-db/__tests__/cli.test.ts index 2fe5578..18fd039 100644 --- a/packages/create-db/__tests__/cli.test.ts +++ b/packages/create-db/__tests__/cli.test.ts @@ -2,9 +2,13 @@ import { describe, it, expect } from "vitest"; import { execa } from "execa"; import { fileURLToPath } from "url"; import path from "path"; +import fs from "fs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CLI_PATH = path.resolve(__dirname, "../dist/cli.mjs"); +const { version: PACKAGE_VERSION } = JSON.parse( + fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf8") +); const runCli = async ( args: string[] = [], @@ -43,13 +47,13 @@ describe("CLI help and version", () => { it("displays version with --version flag", async () => { const result = await runCli(["--version"]); expect(result.exitCode).toBe(0); - expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); + expect(result.stdout.trim()).toBe(PACKAGE_VERSION); }); it("displays version with -V flag", async () => { const result = await runCli(["-V"]); expect(result.exitCode).toBe(0); - expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); + expect(result.stdout.trim()).toBe(PACKAGE_VERSION); }); it("displays create command help", async () => { diff --git a/packages/create-db/src/index.ts b/packages/create-db/src/index.ts index e4d788e..87c7cd9 100644 --- a/packages/create-db/src/index.ts +++ b/packages/create-db/src/index.ts @@ -10,6 +10,7 @@ import { CreateFlags, RegionsFlags, } from "./cli/flags.js"; +import { version } from "../package.json"; import { getCommandName } from "./core/database.js"; import { handleCreate, handleRegions } from "./cli/commands/index.js"; import { createDatabase, fetchRegions } from "./core/services.js"; @@ -55,7 +56,7 @@ export function createDbCli() { return createCli({ router, name: getCommandName(), - version: "1.1.0", + version, description: "Instantly create a temporary Prisma Postgres database", }); } diff --git a/packages/create-db/tsconfig.json b/packages/create-db/tsconfig.json index 228bbeb..4f6a8b4 100644 --- a/packages/create-db/tsconfig.json +++ b/packages/create-db/tsconfig.json @@ -4,6 +4,7 @@ "module": "ESNext", "moduleResolution": "bundler", "esModuleInterop": true, + "resolveJsonModule": true, "verbatimModuleSyntax": true, "strict": true, "skipLibCheck": true,