fix(cli): make stash encrypt work in Prisma Next projects - #819
Conversation
🦋 Changeset detectedLatest commit: 977140b The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📝 WalkthroughWalkthroughPrisma Next encryption commands now derive encryption context from emitted ChangesPrisma Next encryption flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant loadEncryptionContext
participant PrismaNextDetector
participant ContractFile
participant SchemaAdapter
participant EncryptionClient
loadEncryptionContext->>PrismaNextDetector: detect Prisma Next
PrismaNextDetector->>ContractFile: locate emitted contract.json
loadEncryptionContext->>SchemaAdapter: deriveStackSchemasV3(contract)
SchemaAdapter->>EncryptionClient: Encryption({ schemas })
EncryptionClient-->>loadEncryptionContext: return client and tables
sequenceDiagram
participant backfillCommand
participant PostgreSQL
participant installMigrationsSchema
participant runBackfill
backfillCommand->>PostgreSQL: acquire connection
backfillCommand->>installMigrationsSchema: initialize migrations schema
installMigrationsSchema->>PostgreSQL: create cipherstash.cs_migrations if absent
backfillCommand->>runBackfill: execute encryption backfill
Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Enables stash encrypt * commands (notably stash encrypt backfill) to run in Prisma Next projects by deriving schemas from the emitted contract.json when no encryption client file exists, and by ensuring the migrations tracking table (cipherstash.cs_migrations) is bootstrapped before backfill writes events.
Changes:
- Add Prisma Next detection + contract-based schema derivation to
loadEncryptionContextas a fallback when the configured client file is missing. - Ensure
cipherstash.cs_migrationsexists by callinginstallMigrationsSchemaearly inbackfillCommand. - Add unit tests covering Prisma Next derivation paths and the backfill bootstrap ordering.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/cli/src/commands/encrypt/context.ts | Adds Prisma Next contract discovery + schema derivation fallback when the encryption client file is missing. |
| packages/cli/src/commands/encrypt/backfill.ts | Bootstraps cipherstash.cs_migrations before any backfill event inserts. |
| packages/cli/src/commands/encrypt/tests/prisma-next-context.test.ts | Adds tests for Prisma Next context derivation and for migrations schema bootstrap ordering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
`stash encrypt backfill` (and the other encrypt commands) could not run against a Prisma Next project, for two independent reasons: 1. `loadEncryptionContext` hard-failed when the configured encrypt client file was missing — but Prisma Next integrations deliberately have no client file: encrypted columns are declared in the PSL contract and the runtime derives the schemas from the emitted contract.json. Add a fallback pass (mirroring the existing Drizzle auto-derive) that detects a Prisma Next project via `detectPrismaNext`, locates contract.json, and reuses the adapter's own `deriveStackSchemasV3` + `Encryption` to build the context. Both packages are resolved from the user's project so the CLI's schema view always matches the application's. 2. `cipherstash.cs_migrations` never existed: it is created by `stash eql install`, which the Prisma Next flow skips (EQL installs through the prisma-next migration graph, which doesn't carry the tracking schema). The first `appendEvent` insert then failed with an opaque relation-does-not-exist error. Bootstrap the schema in `backfillCommand` via the existing idempotent `installMigrationsSchema` before any event is written. Also documents both in `skills/stash-cli` — notably that the `client` config option is not required in a Prisma Next project, which that skill's option table previously implied it was — and adds the changeset. Validated end-to-end against a Prisma Next app (Next.js 16, contract- declared BigIntOrd + TextSearch twins): backfill derives the schemas with no client file, creates cs_migrations, converges, and `encrypt status` reports both columns as backfilled v3.
f513306 to
1f6b16b
Compare
Addresses Copilot review on #819. - `installMigrationsSchema` failures fell through to the generic catch, which prints only "Backfill failed (…)" by design (its message path can carry plaintext). That is the exact opaque error this bootstrap exists to remove. Wrap it in `BackfillConfigError` naming the cause and the likely fix (CREATE on the database), which the handler prints verbatim — safe here because the DDL touches no row data. - `deriveStackSchemasV3`'s array return type is an assertion, not a checked fact: it arrives through `jiti.import()` behind an `as` cast, so a version skew that reshaped the export would hand back `undefined` and the `schemas.length` read would throw a bare TypeError past all of the surrounding guidance. Guard with `Array.isArray` and name the skew. - `requireUsableEncryptConfig` phrased both refusals as "Encryption client in <path>", so the derived path produced "Encryption client in …/contract.json" — pointing users at a file Prisma Next projects are not supposed to author. Add an optional `sourceLabel` (defaulting to the existing wording, so the client-file caller is unchanged) and pass a derived-source label. - `skills/stash-prisma` gained an "Encrypting data that already exists" section: the encrypt lifecycle needs no client file here, and backfill bootstraps `cs_migrations` itself. The review asked for this skill as well as `stash-cli` (already updated in the previous commit). - Dropped a stale `#<issue>` placeholder from the test header. Two tests added: the non-array guard, and that a bootstrap failure explains itself instead of reporting "Backfill failed". cli suite 938.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts (1)
137-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the "package not installed" failure branch.
The suite covers missing-contract and empty-schema errors, but not the case where
jiti.import('@cipherstash/stack-prisma/v3')(or@cipherstash/stack/v3) throws — the "Failed to load@cipherstash/stack-prisma/@cipherstash/stack" branch incontext.ts(lines 237-244). The existingjitiImportMockalready throws for unexpected specifiers, so a test could simply mockdetectPrismaNextMock/existsSyncas in the success test and makejitiImportMockreject to assert the exit(1) + message.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts` around lines 137 - 200, The Prisma Next context tests lack coverage for the package-load failure path. Add a test in the `loadEncryptionContext — Prisma Next contract derivation` suite that configures `detectPrismaNextMock` and `fsMocks.existsSync` like the success case, makes `jitiImportMock` reject for the stack package import, and asserts `loadEncryptionContext()` exits with code 1 and logs the “Failed to load `@cipherstash/stack-prisma` / `@cipherstash/stack`” guidance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/commands/encrypt/context.ts`:
- Around line 262-271: Wrap the `Encryption({ schemas })` call in
`tryLoadPrismaNextContext` with the function’s existing friendly error handling,
catching initialization failures and reporting them via the Prisma Next–specific
`console.error` followed by `process.exit(1)`. Preserve the subsequent
`requireUsableEncryptConfig(client.getEncryptConfig(), contractPath)` path for
successful initialization and include the caught error details in the guided
message.
---
Nitpick comments:
In `@packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts`:
- Around line 137-200: The Prisma Next context tests lack coverage for the
package-load failure path. Add a test in the `loadEncryptionContext — Prisma
Next contract derivation` suite that configures `detectPrismaNextMock` and
`fsMocks.existsSync` like the success case, makes `jitiImportMock` reject for
the stack package import, and asserts `loadEncryptionContext()` exits with code
1 and logs the “Failed to load `@cipherstash/stack-prisma` / `@cipherstash/stack`”
guidance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1a0299bb-478e-490c-a7b6-e4f8b0681814
📒 Files selected for processing (5)
.changeset/prisma-next-encrypt-backfill.mdpackages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.tspackages/cli/src/commands/encrypt/backfill.tspackages/cli/src/commands/encrypt/context.tsskills/stash-cli/SKILL.md
… one command that derives Two follow-ups from the #819 review. `tryLoadPrismaNextContext` caught every failure mode except the last one. `Encryption({ schemas })` throws on invalid credentials or a bad keyset — a plausible first run — and `loadEncryptionContext()` is called at `backfillCommand`'s line 114, before that command's own try/catch at 131. So the one unguarded call in the function was also the one whose throw escaped as a raw stack trace. The client-file path never had this hole: there the client is constructed inside the user's module, which `jiti.import` already wraps. Now caught, with the message pointing at credentials rather than the contract, since the schemas by definition derived fine to reach that line. Both skills claimed "the encrypt commands" detect Prisma Next. Only `encrypt backfill` does — it is the sole caller of `loadEncryptionContext`. `encrypt status` and `encrypt drop` load `stash.config.ts` alone and resolve against the database, so they never read a client file and never needed the derivation. Named the single command in `stash-cli` and `stash-prisma`, and said what the other two do instead. Tests: 939 passed (61 files), up one for the new guard.
Fixes #851.
Problem
stash encrypt backfillcannot run against a Prisma Next project. Found while dogfoodingstash@1.0.0-rc.4+ the Prisma Next adapter at rc.4 on a Next.js 16 app; both failures reproduce on this branch.Two independent causes:
loadEncryptionContexthard-fails on the missing client file. Prisma Next integrations deliberately have nosrc/encryption/index.ts— the schema lives in the emittedcontract.jsonand the runtime derives it viacipherstashFromStack. The CLI's only schema source is the client file, so every encrypt command dies withEncrypt client file not foundon a correctly set-up project.cipherstash.cs_migrationsnever exists. It's created bystash eql install, which the Prisma Next flow skips (EQL installs through the prisma-next migration graph — which doesn't carry the tracking schema). Even with a hand-written bridge client file, the firstappendEventinsert fails with an opaqueBackfill failed (error).Fix
context.ts: when the client file is missing, a newtryLoadPrismaNextContextpass (mirroring the existing Drizzle auto-derive pass) detects a Prisma Next project (detectPrismaNext), locatescontract.json(src/prisma/,prisma/, project root), and reuses the adapter's own publicderiveStackSchemasV3+Encryptionto build the context. Both packages resolve from the user's project via jiti, so the CLI's schema view always matches the application's. Inside a detected Prisma Next project, missing pieces are hard errors naming the fix (prisma-next contract emit, install the packages) rather than falling through to the misleading client-file error.backfill.ts: bootstrapcs_migrationsvia the existing idempotentinstallMigrationsSchemaright after connecting, before any event insert. No-op everywhereeql installalready ran.skills/stash-cli: documents both. Its configuration table previously implied theclientoption was required forencrypt backfill; it now says Prisma Next projects need no client file and explains where the schemas come from.Review fixes (Copilot)
installMigrationsSchemaerrors fell through to the generic catch, which by design prints onlyBackfill failed (…)because its message path can carry plaintext — the exact error this bootstrap exists to remove. Now wrapped inBackfillConfigErrornaming the cause and likely fix (CREATE on the database), printed verbatim; safe because the DDL touches no row data.deriveStackSchemasV3's array type was an assertion, not a fact. It arrives viajiti.import()behind anascast, so a version skew reshaping the export would returnundefinedand theschemas.lengthread would throw a bare TypeError past all the surrounding guidance. Guarded withArray.isArray, naming the skew.requireUsableEncryptConfigmisnamed its source. It phrased both refusals as "Encryption client in<path>", so the derived path read "Encryption client in …/contract.json" — pointing users at a file they must not author. Added an optionalsourceLabel(defaulting to the existing wording, so the client-file caller is byte-identical) and pass a derived-source label.skills/stash-prismagained an "Encrypting data that already exists" section. The review asked for this skill too, not juststash-cli.#<issue>placeholder from the test header.Tests
installMigrationsSchemaruns beforerunBackfillpackages/clisuite: 938 passed (61 files);code:check0 errorsEnd-to-end validation
Against a live Prisma Next app (contract-declared
BigIntOrd+TextSearchtwins, no client file):Rebase onto
main— two API changes folded inThis branch originally targeted
remove_v2; when that merged as a squash the base retargeted tomainbut the fork point did not, leaving 99 commits and a conflicted diff. Rebased ontomaindown to this PR's own commit. Two changes that landed onmainmeanwhile required real fixes here, neither of which the type-checker could catch — both call sites arejiti.import()with string specifiers behind anascast:@cipherstash/prisma-next/v3→@cipherstash/stack-prisma/v3(the package rename, feat: rename @cipherstash/prisma-next to @cipherstash/stack-prisma for 1.0 #844)EncryptionV3→Encryption—@cipherstash/stack/v3no longer exportsEncryptionV3;Encryptionis now the single client factory (same{ schemas }signature, still async)Left as-is:
detectPrismaNextalready matches@cipherstash/stack-prismaonmain, andprisma-next contract emit/prisma-next migrateare upstream framework CLI commands, unaffected by our package rename.Also added the missing changeset (
stashpatch) — this branch had none, so the fix would have shipped invisibly.Both commits are GPG-signed.
Notes
encrypt status/dropget the derivation for free (sharedloadEncryptionContext).extensionPacks:but the@prisma-next/postgres/configfaçade takesextensions:;eqlMatch's 3-char tokenizer floor is undocumented. Happy to file separately.Related: #820 (
db initadditive-policy fix, same dogfooding run).