Skip to content

feat(cli): add prisma postgres link command#29352

Merged
aqrln merged 14 commits intomainfrom
feat/prisma-postgres-link
Mar 25, 2026
Merged

feat(cli): add prisma postgres link command#29352
aqrln merged 14 commits intomainfrom
feat/prisma-postgres-link

Conversation

@kristof-siket
Copy link
Copy Markdown
Contributor

@kristof-siket kristof-siket commented Mar 18, 2026

Purpose of change

Adds a new prisma postgres link command that connects a local project to a Prisma Postgres database. This is the first command in a new prisma postgres command group for managing Prisma Postgres databases directly from the CLI.

This command is designed as a composable building block — it does one thing (link a project to a remote database) and does it well. A future prisma bootstrap command will orchestrate link alongside init, migrate dev, and db seed to provide a full zero-to-running setup experience.

What's changed and why

New command group: prisma postgres

  • PostgresCommand router in packages/cli/src/postgres/PostgresCommand.ts, following the DbCommand pattern
  • Registered in bin.ts and added to CLI help text

prisma postgres link command (packages/cli/src/link/)

  • Link.ts — three usage modes (see below), argument validation, idempotency check, expired-session retry
  • managementApi.ts — Management API SDK integration for creating connections, listing projects and databases; error mapping for auth and not-found responses; credential sanitization
  • localSetup.ts — creates or updates .env with DATABASE_URL (single-quoted to prevent $ expansion); warns if .gitignore doesn't include .env
  • completionOutput.ts — context-aware next-step guidance depending on whether the schema already has models

Authentication

  • Uses the existing @prisma/management-api-sdk for all API calls — no raw fetch
  • API key mode: createManagementApiClient with static token
  • Browser OAuth mode: createAuthenticatedManagementAPI with automatic token refresh
  • Expired refresh tokens are detected and trigger a transparent re-authentication via browser

Tests (47 vitest tests across 3 test files + 4 in PostgresCommand)

  • Link.vitest.ts — flag validation, non-interactive success, env var fallback, idempotency, interactive mode (project/database selection), auto-select single database, expired session retry
  • managementApi.vitest.ts — connection creation, endpoint fallbacks, error mapping (auth-failed/not-found/generic), list projects, list databases, credential sanitization
  • localSetup.vitest.ts — .env create/update/upsert, $ expansion safety, .gitignore detection
  • PostgresCommand.vitest.ts — help display, subcommand dispatch, unknown subcommand

Usage

The command supports three usage modes:

1. Full interactive (no arguments)

Opens a browser for authentication, then lets you pick a project and database interactively:

npx prisma postgres link
  • Authenticates via browser (or reuses stored tokens)
  • Shows a project picker (arrow keys to navigate)
  • Shows a database picker (or auto-selects if only one exists)
  • Writes DATABASE_URL to .env

2. Semi-interactive (database ID only)

Authenticates via browser but skips the selection prompts:

npx prisma postgres link --database "db_..."

3. Non-interactive (API key + database ID)

For CI/automation — no browser, no prompts:

npx prisma postgres link --api-key "<your-api-key>" --database "db_..."

# Or via environment variable
PRISMA_API_KEY="<your-api-key>" npx prisma postgres link --database "db_..."

How to verify

  1. prisma postgres --help shows the link subcommand
  2. prisma postgres link --help shows usage, all flags, and examples for each mode
  3. Interactive mode: prisma postgres link opens browser auth, then project → database selection
  4. Non-interactive mode: --api-key + --database links without prompts
  5. Missing --database when --api-key is provided returns a clear error
  6. Invalid database ID (without db_ prefix) is rejected with guidance
  7. Successful link creates/updates .env with direct connection string (db.prisma.io)
  8. Re-running on an already-linked project shows "already linked" with --force hint
  9. --force overwrites an existing link
  10. API errors (401, 404) produce user-friendly messages without leaking credentials
  11. Expired OAuth session auto-retries with fresh browser login

Summary by CodeRabbit

  • New Features

    • Added prisma postgres top-level command with prisma postgres link to connect local projects to Prisma Postgres databases.
    • Interactive and non-interactive flows: --api-key, --database, and --force flags; browser-based auth with automatic retry on expired sessions.
    • Writes/updates local .env, checks .gitignore, shows tailored completion/next-step guidance, and redacts sensitive connection info. --api-key is now treated as a sensitive option and is redacted.
  • Tests

    • Comprehensive suites covering linking flows, local setup, management API interactions, CLI dispatch, and help output.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a top-level postgres CLI command and a link subcommand with full linking flow (flag parsing, API/browser auth with retry, dev connection creation), local .env/.gitignore management, management-API helpers, sensitive-option redaction update, completion output formatting, and comprehensive Vitest tests.

Changes

Cohort / File(s) Summary
CLI surface & registration
packages/cli/src/CLI.ts, packages/cli/src/bin.ts
Help text updated to include postgres; bin.ts registers PostgresCommand as a top-level CLI subcommand.
Postgres command router & tests
packages/cli/src/postgres/PostgresCommand.ts, packages/cli/src/postgres/__tests__/PostgresCommand.vitest.ts
Adds PostgresCommand that routes prisma postgres <subcommand> to injected commands, handles --help/errors; tests cover help, dispatch, and unknown-subcommand behavior.
Link command implementation & tests
packages/cli/src/link/Link.ts, packages/cli/src/link/__tests__/Link.vitest.ts
Implements Link CLI: parses --api-key, --database, --force, --help; interactive project/database selection; API/browser auth with retry on expired sessions; idempotency (isAlreadyLinked/--force); writes local files and formats output; extensive tests added.
Management API wrapper & tests
packages/cli/src/link/management-api.ts, packages/cli/src/link/__tests__/management-api.vitest.ts
New API helpers: createDevConnection, listProjects, listDatabases, sanitizeErrorMessage, and LinkApiError with mapped status codes; tests cover connection-string selection, error mapping, listings, and redaction.
Local setup utilities, completion output & tests
packages/cli/src/link/local-setup.ts, packages/cli/src/link/completion-output.ts, packages/cli/src/link/__tests__/local-setup.vitest.ts
Adds env upsert/check utilities (upsertEnvFile, checkGitignore, isAlreadyLinked, writeLocalFiles, formatEnvSummary) and formatCompletionOutput; tests validate .env creation/update, gitignore checks, link detection, and completion text.
Telemetry/redaction
packages/cli/src/utils/checkpoint.ts
Adds --api-key to SENSITIVE_CLI_OPTIONS so redactCommandArray() redacts --api-key values in telemetry.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding a new 'prisma postgres link' command to the CLI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prisma-postgres-link

Comment @coderabbitai help to get the list of available commands and usage tips.

Adds a new `prisma postgres` command group with a `link` subcommand that
connects a local project to a Prisma Postgres database by creating a dev
connection via the Management API and writing connection strings to .env.

New files:
- PostgresCommand router (packages/cli/src/postgres/) following DbCommand pattern
- Link command with --api-key and --database flags (packages/cli/src/link/)
- Management API client with connection reuse (idempotent by hostname)
- Local file setup: .env upsert with single-quoted values, .gitignore check
- Next-step guidance based on schema state

Key behaviors:
- Accepts workspace API key via --api-key flag or PRISMA_API_KEY env var
- Validates database ID format (db_ prefix)
- Creates dev connection or reuses existing one by hostname
- Propagates 401/403 from list connections immediately
- Sanitizes credentials in error messages
- 39 vitest tests across 4 files
@kristof-siket kristof-siket force-pushed the feat/prisma-postgres-link branch from c43fd3c to 44a694e Compare March 18, 2026 15:24
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 18, 2026

size-limit report 📦

Path Size
packages/client/runtime/index-browser.js 2.29 KB (0%)
packages/client/runtime/index-browser.d.ts 3.37 KB (0%)
packages/cli/build/index.js 2.52 MB (+0.33% 🔺)
packages/client/prisma-client-0.0.0.tgz 26.81 MB (-0.01% 🔽)
packages/cli/prisma-0.0.0.tgz 13.52 MB (+0.04% 🔺)
packages/bundle-size/da-workers-libsql/output.tgz 1.33 MB (-0.01% 🔽)
packages/bundle-size/da-workers-neon/output.tgz 1.39 MB (-0.01% 🔽)
packages/bundle-size/da-workers-pg/output.tgz 1.38 MB (-0.01% 🔽)
packages/bundle-size/da-workers-planetscale/output.tgz 1.33 MB (-0.01% 🔽)
packages/bundle-size/da-workers-d1/output.tgz 1.3 MB (-0.01% 🔽)

Replace `async () =>` with `() => Promise.resolve()` in mock return
values to satisfy @typescript-eslint/require-await rule. Remove unused
`result` variable and `HelpError` import flagged by no-unused-vars.
- Set DATABASE_URL to the direct connection string instead of pooled,
  matching `prisma init --db` behavior and enabling `prisma migrate dev`
  to work out of the box without directUrl config
- Remove findExistingConnection (list endpoint doesn't return connection
  strings by design, making the reuse path dead code)
- Add .env-based idempotency: skip API call if DATABASE_URL already
  points to Prisma Postgres, suggest --force to re-link
- Remove DIRECT_URL from .env output (no longer needed)
- Simplify ConnectionResult to single connectionString field
Add fallback chain for connection string extraction: direct → pooled →
accelerate → deprecated connectionString field. Ensures the command
works regardless of which endpoint type the Management API returns.
- Refactor managementApi.ts to use Management API SDK client instead of
  raw fetch, unifying API interaction for both API key and OAuth paths
- Add resolveApiClient: uses static token when --api-key is provided,
  falls back to browser OAuth authentication otherwise
- Add resolveDatabase: interactive project and database selection via
  @inquirer/prompts when --database is not specified
- Support three usage modes:
  1. Full interactive (no args): browser auth + project/database picker
  2. Semi-interactive (--database only): browser auth + explicit DB
  3. Non-interactive (--api-key + --database): CI/automation use case
- Auto-select database when only one exists in the chosen project
- Add listProjects and listDatabases API helpers
- Update all tests for new SDK-based signatures and interactive flows
The Management API returns `authentication-failed` (not `unauthorized`)
for invalid tokens. Add this code to the error mapping so users see
the actionable "check your API key" message.
When stored OAuth tokens have an expired refresh token, the SDK throws
an AuthError with refreshTokenInvalid=true. Previously this surfaced
as a cryptic "invalid_grant: Invalid grant" message. Now the command
detects this, prints "Session expired", triggers a fresh browser login,
and retries the operation transparently.
@kristof-siket
Copy link
Copy Markdown
Contributor Author

Test Report: prisma postgres link — CLI Feature QA

PR: #29352
Tested on: Local build from source (packages/cli/src/bin.ts via tsx)
Baseline: N/A (new command, no production baseline)
API Target: Production Management API (https://api.prisma.io)
Date: 2026-03-19

What changed

Adds a new prisma postgres link command with three usage modes: non-interactive (--api-key + --database), semi-interactive (--database only, browser auth), and full interactive (no args — browser auth + project/database selection). Uses the Management API SDK for all API calls, with automatic retry on expired OAuth sessions.

Test Infrastructure

Resource Value
Workspace cmhki6m13022a3qfm0w74ezop ("Helpful Purple Beaver")
Project (1 DB) "CLI Link QA - Project Alpha" — db_cmmx8hich0z3izyfssp1zr26c
Project (2 DBs) "bad beach" — db_cmlyxymcq0e80ymecffboahx4, db_cmmiz7mmr01f1zmfpirdd81io
API Token Generated via @pdp/auth token script against production

Results: 81/81 PASS

Unit Tests (vitest) — 47/47 PASS

Executed by: Agent

Suite Tests Status
Link.vitest.ts 15 (help, validation, non-interactive, idempotency, interactive, expired session retry) PASS
managementApi.vitest.ts 14 (connection creation, fallbacks, error mapping, list helpers, sanitization) PASS
localSetup.vitest.ts 18 (.env upsert, gitignore detection, idempotency check) PASS

Help & Command Routing (6/6 PASS)

Executed by: Agent — automated against real CLI binary

ID Command Expected Status
T-01 prisma postgres --help Shows help with link subcommand PASS
T-02 prisma postgres (no args) Shows help PASS
T-03 prisma postgres foo "Unknown command" error PASS
T-04 prisma postgres link --help Shows link-specific help with all flags PASS
T-05 prisma postgres link -h Same as --help PASS
T-06 prisma postgres link --bad-flag "unexpected option" error PASS

Argument Validation (3/3 PASS)

Executed by: Agent

ID Command Expected Status
T-07 --api-key without --database Error: "Missing --database flag" PASS
T-08 PRISMA_API_KEY=x without --database Same error PASS
T-09 --database "invalid_id" (no db_ prefix) Error: "Invalid database ID" PASS

Idempotency (5/5 PASS)

Executed by: Agent

ID Scenario Expected Status
T-10 .env has DATABASE_URLdb.prisma.io "already linked", no API call PASS
T-11 .env has DATABASE_URLdb-pool.prisma.io "already linked" PASS
T-12 Already linked + --force Re-links, overwrites .env PASS
T-13 .env with non-PPG DATABASE_URL Proceeds, updates PASS
T-14 No .env at all Creates .env PASS

Non-Interactive Success — --api-key + --database (6/6 PASS)

Executed by: Agent — against production Management API

ID Test Expected Status
T-15 Happy path with explicit flags "linked successfully", .env created PASS
T-16 PRISMA_API_KEY env var fallback Same success PASS
T-17 .env contains direct URL (db.prisma.io) Not pooled URL PASS
T-18 Connection string single-quoted Prevents $ expansion PASS
T-19 Schema with models → next steps Shows "prisma generate" then "prisma migrate dev" PASS
T-20 No schema → next steps Shows "Define your data model" PASS

Non-Interactive Errors (3/3 PASS)

Executed by: Agent — against production Management API

ID Test Expected Status
T-21 Invalid API key "Invalid credentials" (actionable message) PASS
T-22 Non-existent database ID "not found" error PASS
T-23 Unreachable API URL Error shown, no credentials leaked PASS

.env and .gitignore Behavior (5/5 PASS)

Executed by: Agent

ID Scenario Expected Status
T-24 .gitignore includes .env No warning PASS
T-25 .gitignore exists without .env Warning: "does not include .env" PASS
T-26 No .gitignore file Warning: "No .gitignore found" PASS
T-27 Other env vars in .env Preserved after link PASS
T-28 New .env values single-quoted DATABASE_URL='postgres://...' PASS

Browser OAuth Authentication (2/2 PASS)

Executed by: Human (requires real browser interaction)

ID Test Expected Status
T-29 No stored tokens → --database flag only Browser opens, auth succeeds, links successfully PASS
T-30 Stored tokens → --database flag only No browser opens, links immediately PASS

Full Interactive Mode (2/2 PASS)

Executed by: Human (requires TTY for prompt navigation)

ID Test Expected Status
T-31 Project with 1 database → auto-select Project picker → select → "Using database X" auto-message → links PASS
T-32 Project with 2 databases → database picker Project picker → database picker → select → links PASS

Misc (2/2 PASS)

Executed by: Agent

ID Test Expected Status
T-33 Default API URL (api.prisma.io) Links successfully PASS
T-34 .gitignore warning in output Present when no .gitignore PASS

Bugs Found & Fixed During QA

1. authentication-failed error code not mapped (fixed in 74a271cd5)

Before: The Management API returns error code authentication-failed for invalid tokens, but the CLI only checked for unauthorized. The generic fallback message "Invalid authorization token" was shown — functional but not actionable.

After: Both unauthorized and authentication-failed codes now map to: "Invalid credentials — check your API key or re-authenticate via browser."

2. Expired refresh token crashed with cryptic invalid_grant (fixed in 0dd178f68)

Before: When stored OAuth tokens had an expired refresh token, the SDK threw an unhandled AuthError and the CLI showed ! invalid_grant: Invalid grant — confusing and unrecoverable without knowing the credentials file location (~/Library/Preferences/prisma-platform/auth.json).

After: The command detects expired sessions (AuthError.refreshTokenInvalid), prints "Session expired. Re-authenticating via browser...", opens a fresh browser login, and retries the operation transparently. Covered by a new vitest test case.


Key Observations

  • Credential storage location: Stored at ~/Library/Preferences/prisma-platform/auth.json on macOS (via xdg-app-paths), not ~/.prisma/credentials. Users attempting manual cleanup need to know this path.
  • Wrong workspace during auth: If a user authenticates via browser and selects a workspace that doesn't own the target database, the error says "Invalid credentials" — technically correct but could be more helpful. Noted as a follow-up UX improvement.
  • Direct connection by default: DATABASE_URL is set to the direct connection string (db.prisma.io), not pooled — this makes prisma migrate dev work immediately without extra directUrl configuration.
  • Single-database auto-select: When a project has exactly one ready database, the CLI skips the picker and auto-selects with a confirmation message — nice UX touch confirmed working.

@kristof-siket kristof-siket marked this pull request as ready for review March 19, 2026 10:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78948842-b73f-4d50-a215-0c572dce3112

📥 Commits

Reviewing files that changed from the base of the PR and between 33667c3 and 0dd178f.

📒 Files selected for processing (12)
  • packages/cli/src/CLI.ts
  • packages/cli/src/bin.ts
  • packages/cli/src/link/Link.ts
  • packages/cli/src/link/__tests__/Link.vitest.ts
  • packages/cli/src/link/__tests__/localSetup.vitest.ts
  • packages/cli/src/link/__tests__/managementApi.vitest.ts
  • packages/cli/src/link/completionOutput.ts
  • packages/cli/src/link/index.ts
  • packages/cli/src/link/localSetup.ts
  • packages/cli/src/link/managementApi.ts
  • packages/cli/src/postgres/PostgresCommand.ts
  • packages/cli/src/postgres/__tests__/PostgresCommand.vitest.ts

- Remove barrel file (index.ts), import Link directly in bin.ts
- Fix PRISMA_API_KEY env var breaking zero-arg interactive flow: only
  use env var as fallback when --database is also provided
- Sanitize LinkApiError messages through sanitizeErrorMessage()
- Rename utility files to kebab-case (local-setup, management-api,
  completion-output) matching CLI conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5217b79a-3c26-49be-afb7-0b5a16c0b66c

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd178f and 271fca6.

📒 Files selected for processing (8)
  • packages/cli/src/bin.ts
  • packages/cli/src/link/Link.ts
  • packages/cli/src/link/__tests__/Link.vitest.ts
  • packages/cli/src/link/__tests__/local-setup.vitest.ts
  • packages/cli/src/link/__tests__/management-api.vitest.ts
  • packages/cli/src/link/completion-output.ts
  • packages/cli/src/link/local-setup.ts
  • packages/cli/src/link/management-api.ts

- Fix prettier formatting in Link.vitest.ts
- Escape regex key in upsertEnvFile to prevent ReDoS if keys become
  user-supplied in the future

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d5a2378-7191-4bee-b4d0-6378b2432e82

📥 Commits

Reviewing files that changed from the base of the PR and between 271fca6 and 9e1379a.

📒 Files selected for processing (2)
  • packages/cli/src/link/__tests__/Link.vitest.ts
  • packages/cli/src/link/local-setup.ts

- Use replacer callback in upsertEnvFile to prevent $1/$& regex
  replacement token corruption in connection strings
- Use global flag to replace all duplicate key assignments
- Accept /.env* (root-anchored wildcard) in .gitignore check
- Add JSDoc to all exported items in local-setup.ts
- Assert auth path (not just success output) in PRISMA_API_KEY test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1fc3b825-da45-47e2-b3ec-15f866863a56

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1379a and 084908e.

📒 Files selected for processing (2)
  • packages/cli/src/link/__tests__/Link.vitest.ts
  • packages/cli/src/link/local-setup.ts

Prevents the --api-key value from being sent in plaintext to the
checkpoint telemetry server and the Rust panic handler.
@aqrln aqrln added this to the 7.6.0 milestone Mar 24, 2026
@aqrln aqrln self-assigned this Mar 24, 2026
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Mar 25, 2026
@aqrln aqrln merged commit 45d7e0f into main Mar 25, 2026
241 of 242 checks passed
@aqrln aqrln deleted the feat/prisma-postgres-link branch March 25, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants