feat(cli): add prisma postgres link command#29352
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a top-level Changes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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
c43fd3c to
44a694e
Compare
size-limit report 📦
|
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.
Test Report:
|
| 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_URL → db.prisma.io |
"already linked", no API call | PASS |
| T-11 | .env has DATABASE_URL → db-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.jsonon macOS (viaxdg-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_URLis set to the direct connection string (db.prisma.io), not pooled — this makesprisma migrate devwork immediately without extradirectUrlconfiguration. - 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.
There was a problem hiding this comment.
Actionable comments posted: 7
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 78948842-b73f-4d50-a215-0c572dce3112
📒 Files selected for processing (12)
packages/cli/src/CLI.tspackages/cli/src/bin.tspackages/cli/src/link/Link.tspackages/cli/src/link/__tests__/Link.vitest.tspackages/cli/src/link/__tests__/localSetup.vitest.tspackages/cli/src/link/__tests__/managementApi.vitest.tspackages/cli/src/link/completionOutput.tspackages/cli/src/link/index.tspackages/cli/src/link/localSetup.tspackages/cli/src/link/managementApi.tspackages/cli/src/postgres/PostgresCommand.tspackages/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>
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5217b79a-3c26-49be-afb7-0b5a16c0b66c
📒 Files selected for processing (8)
packages/cli/src/bin.tspackages/cli/src/link/Link.tspackages/cli/src/link/__tests__/Link.vitest.tspackages/cli/src/link/__tests__/local-setup.vitest.tspackages/cli/src/link/__tests__/management-api.vitest.tspackages/cli/src/link/completion-output.tspackages/cli/src/link/local-setup.tspackages/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>
There was a problem hiding this comment.
Actionable comments posted: 4
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2d5a2378-7191-4bee-b4d0-6378b2432e82
📒 Files selected for processing (2)
packages/cli/src/link/__tests__/Link.vitest.tspackages/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>
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1fc3b825-da45-47e2-b3ec-15f866863a56
📒 Files selected for processing (2)
packages/cli/src/link/__tests__/Link.vitest.tspackages/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.
Purpose of change
Adds a new
prisma postgres linkcommand that connects a local project to a Prisma Postgres database. This is the first command in a newprisma postgrescommand 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 bootstrapcommand will orchestratelinkalongsideinit,migrate dev, anddb seedto provide a full zero-to-running setup experience.What's changed and why
New command group:
prisma postgresPostgresCommandrouter inpackages/cli/src/postgres/PostgresCommand.ts, following theDbCommandpatternbin.tsand added to CLI help textprisma postgres linkcommand (packages/cli/src/link/)Link.ts— three usage modes (see below), argument validation, idempotency check, expired-session retrymanagementApi.ts— Management API SDK integration for creating connections, listing projects and databases; error mapping for auth and not-found responses; credential sanitizationlocalSetup.ts— creates or updates.envwithDATABASE_URL(single-quoted to prevent$expansion); warns if.gitignoredoesn't include.envcompletionOutput.ts— context-aware next-step guidance depending on whether the schema already has modelsAuthentication
@prisma/management-api-sdkfor all API calls — no rawfetchcreateManagementApiClientwith static tokencreateAuthenticatedManagementAPIwith automatic token refreshTests (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 retrymanagementApi.vitest.ts— connection creation, endpoint fallbacks, error mapping (auth-failed/not-found/generic), list projects, list databases, credential sanitizationlocalSetup.vitest.ts— .env create/update/upsert,$expansion safety, .gitignore detectionPostgresCommand.vitest.ts— help display, subcommand dispatch, unknown subcommandUsage
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:
DATABASE_URLto.env2. 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:
How to verify
prisma postgres --helpshows thelinksubcommandprisma postgres link --helpshows usage, all flags, and examples for each modeprisma postgres linkopens browser auth, then project → database selection--api-key+--databaselinks without prompts--databasewhen--api-keyis provided returns a clear errordb_prefix) is rejected with guidance.envwith direct connection string (db.prisma.io)--forcehint--forceoverwrites an existing linkSummary by CodeRabbit
New Features
prisma postgrestop-level command withprisma postgres linkto connect local projects to Prisma Postgres databases.--api-key,--database, and--forceflags; browser-based auth with automatic retry on expired sessions.--api-keyis now treated as a sensitive option and is redacted.Tests