Skip to content

feat(cli): add stash eql migration --drizzle (v3, migration-first EQL install)#691

Merged
coderdan merged 5 commits into
mainfrom
feat/eql-migration-command
Jul 18, 2026
Merged

feat(cli): add stash eql migration --drizzle (v3, migration-first EQL install)#691
coderdan merged 5 commits into
mainfrom
feat/eql-migration-command

Conversation

@coderdan

@coderdan coderdan commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

PR 1 of 3 in the plan documented at #690 — migration-first, ORM-agnostic EQL v3 install.

Why

EQL install differs per integration, and prisma-next's approach breaks on Supabase (its baked-in full v2 bundle needs superuser for CREATE OPERATOR CLASS/FAMILY). Drizzle avoids this by delegating install to the CLI. This PR makes that delegation a first-class, migration-emitting command — one source of install SQL (the CLI's bundled variants), reusable by every ORM.

stash eql migration --drizzle              # Drizzle custom migration in drizzle/
stash eql migration --drizzle --supabase   # also grants eql_v3 to anon/authenticated/service_role

What

  • New stash eql migration --drizzle [--supabase] [--name] [--out] [--dry-run]. Generates a Drizzle custom migration (drizzle-kit generate --custom, then injects SQL) carrying the bundled EQL v3 install script + the cs_migrations tracking schema — so one drizzle-kit migrate installs everything stash encrypt … needs.
  • --supabase appends the v3 role grants (eql_v3 + eql_v3_internal → anon/authenticated/service_role), matching stash eql install --supabase. Harmless when connecting directly as postgres; needed for PostgREST/RLS.
  • v3 only — no --eql-version (prisma-next never shipped v2).
  • --prisma is registered but fails with a pointer to stash eql migration: migration-first, ORM-agnostic EQL v3 install (fixes prisma-next superuser-on-Supabase) #690 until PR3 (it needs prisma-next's v3 surface from Feat/prisma next eql v3 #655 to install against).

Design

  • The SQL assembly (buildEqlV3MigrationSql) is a pure, unit-tested function; the drizzle-kit scaffold/inject orchestration reuses the proven helpers from the v2 install --drizzle path (findGeneratedMigration, cleanupMigrationFile, now exported — the only change to install.ts).
  • Exactly one of --drizzle/--prisma required; both/neither error cleanly (exit 1).

Sequencing (see #690)

  1. This PR--drizzle, off main. Independent; Drizzle v3 is already on main.
  2. Rebase Feat/prisma next eql v3 #655 (feat/prisma-next-eql-v3) on this.
  3. --prisma emitter + remove prisma-next's baked baseline, stacked on Feat/prisma next eql v3 #655 — that's the change that makes prisma-next Supabase-safe.

Tests

  • 4 new unit tests (buildEqlV3MigrationSql: v3 bundle present, grants gated on --supabase, tracking schema, additive superset).
  • Smoke-tested all four control paths (no-target → exit 1; --prisma → pointer + exit 1; both → exit 1; --drizzle --dry-run → dry-run note, exit 0).
  • 544 CLI unit green, manifest resolves eql migration, biome clean.
  • stash-cli + stash-drizzle skills updated; changeset (stash minor).

Note: the pty e2e suite wasn't run (this command has no interactive flow); its behaviour is covered by the unit + smoke checks above.

https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w

Summary by CodeRabbit

  • New Features
    • Added stash eql migration to generate EQL v3 install migrations for Drizzle.
    • Supports custom migration names, output locations, dry runs, and optional Supabase role grants.
    • Generated migrations include EQL v3 installation and migration tracking setup.
    • Added clear validation messages for missing, conflicting, or unavailable targets.
  • Documentation
    • Updated CLI and Drizzle guidance with migration-based installation instructions and examples.
  • Tests
    • Added coverage for generated SQL and Supabase-specific grants.

Summary by CodeRabbit

  • New Features
    • Added stash eql migration to generate EQL v3 installation migrations for Drizzle.
    • Supports custom migration names, output directories, dry runs, and optional Supabase role grants.
    • Generated migrations include EQL v3 installation and migration-tracking setup.
  • Documentation
    • Added CLI help and updated EQL, Drizzle, and CLI guidance for the migration workflow.
    • Prisma support is registered but not yet available.
  • Bug Fixes
    • Improved migration generation error handling, validation, and cleanup.

Summary by CodeRabbit

  • New Features
    • Added stash eql migration to generate EQL v3 install migrations for Drizzle.
    • Added optional Supabase role grants and migration tracking schema support.
    • Added dry-run, custom name, and output directory options.
    • Added clear validation and error messages, including Prisma availability guidance.
  • Documentation
    • Updated CLI and integration guidance with the new migration workflow.
  • Tests
    • Added coverage for command help, validation, generation, failures, cleanup, and Supabase options.

@coderdan
coderdan requested a review from a team as a code owner July 17, 2026 12:34
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d1b306f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
stash Minor
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@cipherstash/stack Minor
@cipherstash/stack-drizzle Minor
@cipherstash/stack-supabase Minor
@cipherstash/prisma-next Minor
@cipherstash/wizard Minor
@cipherstash/bench Patch
@cipherstash/test-kit Patch
@cipherstash/prisma-next-example Patch

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

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds stash eql migration with Drizzle support, EQL v3 installation and tracking SQL generation, optional Supabase grants, CLI validation and dispatch, migration file handling, tests, and documentation. Prisma is registered but unavailable.

Changes

EQL migration generation

Layer / File(s) Summary
Migration SQL assembly and validation
packages/cli/src/commands/eql/migration.ts, packages/cli/src/commands/eql/__tests__/migration.test.ts, packages/cli/src/messages.ts
Builds EQL v3 SQL with optional Supabase grants and always includes cs_migrations; tests validate content and ordering.
CLI dispatch and Drizzle generation
packages/cli/src/commands/eql/..., packages/cli/src/bin/main.ts, packages/cli/src/cli/registry.ts, packages/cli/src/commands/db/install.ts, packages/cli/src/commands/init/...
Registers and dispatches the command, validates targets, scaffolds a Drizzle custom migration, writes SQL, and handles failures and cleanup.
Migration discovery and validation coverage
packages/cli/src/commands/eql/__tests__/migration.test.ts, packages/cli/src/commands/db/__tests__/find-generated-migration.test.ts
Tests target validation, dry-run behavior, command arguments, SQL writing, error handling, cleanup, and generated migration selection.
Documentation and help coverage
.changeset/eql-migration-command.md, skills/stash-*/SKILL.md, packages/cli/tests/e2e/*
Documents migration usage, Supabase grants, tracking schema installation, Prisma availability, and CLI help output.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new stash eql migration CLI flow for v3 EQL installs, centered on the Drizzle path.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eql-migration-command

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/cli/src/commands/eql/migration.ts (1)

132-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the drizzle-kit installation hint package-manager aware.

Since detectPackageManager() is already imported and available in this file, you can dynamically construct the installation command instead of hardcoding npm. This provides a better experience for users on pnpm, yarn, or bun.

✨ Proposed refactor
-    p.log.info('Make sure drizzle-kit is installed: npm install -D drizzle-kit')
+    const pm = detectPackageManager()
+    const installCmd = pm === 'npm' ? 'npm install -D' : `${pm} add -D`
+    p.log.info(`Make sure drizzle-kit is installed: ${installCmd} drizzle-kit`)
🤖 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/eql/migration.ts` at line 132, Update the
installation hint in the migration command to use the package manager returned
by detectPackageManager() instead of hardcoding npm, while preserving the
existing drizzle-kit development-dependency install wording for pnpm, yarn, bun,
and other supported managers.
🤖 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/eql/migration.ts`:
- Around line 96-100: Validate the user-derived migrationName in the migration
command flow before constructing drizzleCmd, allowing only alphanumeric
characters, dashes, and underscores. Reject invalid names with the command’s
established error-handling behavior, and only interpolate the validated value
into the execSync command.

---

Nitpick comments:
In `@packages/cli/src/commands/eql/migration.ts`:
- Line 132: Update the installation hint in the migration command to use the
package manager returned by detectPackageManager() instead of hardcoding npm,
while preserving the existing drizzle-kit development-dependency install wording
for pnpm, yarn, bun, and other supported managers.
🪄 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

Run ID: dc6bc24f-3a47-4690-b339-ba275d91484e

📥 Commits

Reviewing files that changed from the base of the PR and between 457705c and 6c34978.

📒 Files selected for processing (9)
  • .changeset/eql-migration-command.md
  • packages/cli/src/bin/main.ts
  • packages/cli/src/cli/registry.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/eql/__tests__/migration.test.ts
  • packages/cli/src/commands/eql/migration.ts
  • packages/cli/src/messages.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md

Comment thread packages/cli/src/commands/eql/migration.ts

@coderdan coderdan left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Test coverage review — stash eql migration --drizzle

The added migration.test.ts covers buildEqlV3MigrationSql well (4 tests, both --supabase arms). The gap is everything around it: eqlMigrationCommand and generateDrizzleEqlMigration — the actual command — have zero tests. That's 3 validation exits, the --dry-run short-circuit, the --name/--out threading, and the cleanup-on-write-failure path, none exercised. The file header calls the orchestration "thin and I/O-bound", but packages/cli already has the idiom for exactly this shape — auth/__tests__/login.test.ts (spyExit() + hoisted clack mock) and env/__tests__/env.test.ts — so the cost here is low.

Six inline comments below, ordered by value. All sketches are drop-in for the fast unit suite (pnpm --filter stash test) — I verified the module graph loads without native deps and that the ordering assertion in the third comment passes as written.

Additional coverage gaps not posted inline

  • execSync failure fallback (migration.ts:120-136) — the stderr extraction has a 3-way fallback (error.stderrerror.message'Unknown error.') and none of the three arms is covered. Cheap to pin once the node:child_process mock from comment #2 is in place: execMock.mockImplementation(() => { throw Object.assign(new Error('boom'), { stderr: 'drizzle-kit: not found' }) }) → asserts p.log.error got the trimmed stderr, not 'boom'.
  • cleanupMigrationFile's warn-on-failure arm (db/install.ts:886) — the catchp.log.warn branch is unreachable in any current test.
  • No test guards registry ↔ HELP drift. Not a gap in the diff's own logic, but worth flagging: the new command is in registry.ts but eql migration was not added to the HELP banner in bin/main.ts:110-112, which still lists only eql install / eql upgrade / eql status. stash --help therefore won't mention the command this PR ships. AGENTS.md treats the registry as the source of truth, and manifest.test.ts asserts registry completeness — but nothing asserts the hand-maintained HELP string stays in sync with it, so this drifted silently. Worth a one-line fix plus (optionally) a unit test that every non-hidden registry command name appears in HELP.

Out of scope, noted only

Nothing security- or crypto-relevant. One functional observation surfaced while writing comment #4: --out is resolved for the lookup but never passed to drizzle-kit, so --out only works when it happens to match drizzle.config.ts. Same shape as the pre-existing db/install.ts path, so this PR inherits rather than introduces it — but the registry advertises --out as "Directory to write the generated migration into", which is not what it does today.

Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/db/install.ts

@coderdan coderdan left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewed at extra-high effort — recall-oriented, so some of these are minor. Take them as surfaced, not all as blockers. 10 findings: 9 inline, 1 below.

The two that would bite users immediately

  1. The command runs the wrong drizzle-kit on pnpm and yarn (migration.ts:98) — runnerCommand is the pnpm dlx / yarn dlx download-and-run form, not the run-a-local-binary form. execCommand in setup-prompt.ts:78 already exists for exactly this and is already used for drizzle-kit generate.
  2. --out doesn't write anywhere (migration.ts:97) — it only changes where we search. Any project whose drizzle out dir isn't ./drizzle fails at step 2.

Both are inherited from the v2 install --drizzle path this reuses, so the fix likely belongs in both places.

Not inline (the line isn't in the diff)

eql migration is missing from the global HELP bannerpackages/cli/src/bin/main.ts:110-112.

registry.ts:8-13 calls this out specifically:

⚠️ The one remaining hand-maintained surface is the global HELP banner in bin/main.ts (bare stash / stash --help) — it duplicates only the command list (names + summaries), not their flags. A new command, or a summary edit, still belongs in both places or the banner and stash manifest will diverge.

stash --help, bare stash, and the eql unknown-subcommand fallback (main.ts:258) all print HELP, which lists eql install / eql upgrade / eql status but not eql migration — so the command this PR positions as the preferred v3 install path is invisible in the CLI's primary help. Per-command help (stash eql migration --help) is fine; it reads the registry.


The SQL assembly split — buildEqlV3MigrationSql as a pure, unit-tested function — reads well, and the v3-only / exactly-one-target framing is clean.

Comment thread packages/cli/src/commands/eql/migration.ts Outdated
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts Outdated
Comment thread packages/cli/src/commands/eql/migration.ts Outdated
Comment thread packages/cli/src/commands/eql/migration.ts Outdated
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/eql/__tests__/migration.test.ts Outdated
coderdan added a commit that referenced this pull request Jul 17, 2026
Addresses the review on #691 — correctness, security, telemetry, and coverage:

- Run the PROJECT-LOCAL drizzle-kit (`pnpm exec` / `npx --no-install`), not the
  `pnpm dlx`/`yarn dlx` download-and-run form, so it resolves the project's own
  drizzle.config.ts + schema. New shared `execCommand`/`execArgv` in init/utils
  (setup-prompt's private copy folded in).
- Invoke via `spawnSync` with an argv array instead of `execSync` on a shell
  string — a `--name` with spaces or shell metacharacters is now one inert token
  (no word-splitting, no command injection). Plus a `[\w-]+` name guard.
- Pass `--out` through to `drizzle-kit generate` (always) so the flag actually
  steers where the migration is written, and our lookup can't miss it.
- Validation exits and every abort throw `CliExit(1)` instead of `process.exit`,
  so the telemetry `finally` runs and the branches are unit-testable.
- Guard `loadBundledEqlSql` (corrupt bundle → clean error, not a fatal trace),
  add the missing `p.intro`, and call `printNextSteps()` so the now-preferred
  install path isn't less helpful than `eql install`.
- HELP banner lists `eql migration`.

Tests: 14 migration unit (target selection incl. `--supabase`-alone, name guard,
dry-run, argv threading, non-zero drizzle-kit, write-failure cleanup, SQL order),
3 `findGeneratedMigration` unit (now public API), e2e smoke + `eql migration
--help`. 557 unit / 65 e2e green, code:check clean.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
@coderdan

Copy link
Copy Markdown
Contributor Author

Addressed the full review in fd0af48 — all inline threads replied to + resolved. Summary of the substantive changes:

Correctness / security

  • Run the project-local drizzle-kit (pnpm exec / npx --no-install), not the pnpm dlx download-and-run form (new shared execCommand/execArgv; setup-prompt's private copy folded in).
  • spawnSync with an argv array instead of execSync on a shell string — no word-splitting, no command injection — plus a /^[\w-]+$/ name guard.
  • --out is now passed to drizzle-kit generate so the flag actually steers where the migration lands.
  • Guarded loadBundledEqlSql; added the missing p.intro; restored printNextSteps(); HELP banner lists eql migration.

Telemetry / testability

  • Validation exits and every abort throw CliExit(1) instead of process.exit, so the telemetry finally runs and the branches are assertable.

Coverage (the big gap you flagged)

  • 14 migration unit tests (target selection incl. --supabase-alone, name guard, dry-run, argv/--name/--out threading, non-zero drizzle-kit, write-failure cleanup, SQL ordering).
  • 3 findGeneratedMigration tests (now public API, two consumers).
  • e2e: smoke + command-help extended for eql migration and its --help.

557 unit / 65 e2e green, code:check clean.

One I only mitigated, not fully fixed (called out on its thread): a step-2 "couldn't locate the generated file" failure can't clean a path it never learned. Passing --out removes the common config-mismatch cause; a genuinely unexpected drizzle-kit naming change would still orphan a scaffold, same as the v2 install --drizzle path. Flagging honestly rather than faking a cleanup.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/eql/__tests__/migration.test.ts`:
- Line 43: Update the mock declaration containing real so it does not use the
type-erasing undefined as unknown assertion: make real optional and narrow it
before use, or add a targeted Biome suppression with a reason documenting the
hoisted mock initialization. Preserve the existing writeFileSync mock behavior.

In `@packages/cli/src/commands/init/utils.ts`:
- Around line 257-258: Update the `bun` case in the command selection logic to
include `--no-install` in `prefixArgs` alongside `x`, preventing Bun from
installing missing binaries while preserving project-local execution.
🪄 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

Run ID: 498c1f60-11dc-4ae3-8a05-2ecc6cd2823f

📥 Commits

Reviewing files that changed from the base of the PR and between 6c34978 and fd0af48.

📒 Files selected for processing (11)
  • packages/cli/src/bin/main.ts
  • packages/cli/src/cli/registry.ts
  • packages/cli/src/commands/db/__tests__/find-generated-migration.test.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/eql/__tests__/migration.test.ts
  • packages/cli/src/commands/eql/migration.ts
  • packages/cli/src/commands/init/lib/setup-prompt.ts
  • packages/cli/src/commands/init/utils.ts
  • packages/cli/src/messages.ts
  • packages/cli/tests/e2e/command-help.e2e.test.ts
  • packages/cli/tests/e2e/smoke.e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/cli/src/messages.ts
  • packages/cli/src/bin/main.ts
  • packages/cli/src/cli/registry.ts

Comment thread packages/cli/src/commands/eql/__tests__/migration.test.ts Outdated
Comment thread packages/cli/src/commands/init/utils.ts Outdated
coderdan added 2 commits July 18, 2026 22:24
…QL install)

First of the three PRs in #690: make migration-first, ORM-agnostic EQL v3 install
the front door, so every integration sources install SQL from one place (the CLI's
bundled variants) instead of vendoring its own. This is the pattern that keeps
Drizzle Supabase-safe; prisma-next's superuser-on-Supabase failure is fixed by the
stacked follow-ups (`--prisma` emitter + removing prisma-next's baked baseline),
which land on top of the prisma-next EQL v3 work (#655).

`stash eql migration --drizzle [--supabase] [--name] [--out] [--dry-run]`:
- Generates a Drizzle custom migration (via `drizzle-kit generate --custom`, then
  injects the SQL) carrying the bundled EQL **v3** install script + the
  `cs_migrations` tracking schema — one `drizzle-kit migrate` does everything
  `stash encrypt …` needs.
- `--supabase` appends the v3 role grants (`eql_v3` + `eql_v3_internal` →
  anon/authenticated/service_role), matching `stash eql install --supabase`.
- v3 only — no `--eql-version` (prisma-next never shipped v2).
- `--prisma` is registered but fails with a pointer to #690 until the stacked PR.

The SQL assembly (`buildEqlV3MigrationSql`) is a pure, unit-tested function; the
drizzle-kit scaffold/inject orchestration reuses the proven helpers from the v2
`install --drizzle` path (`findGeneratedMigration`, `cleanupMigrationFile`, now
exported). Registry + dispatch + help + `stash-cli`/`stash-drizzle` skills updated.

Tests: 4 new unit (bundle present, grants gated on --supabase, tracking schema),
544 CLI unit green, manifest resolves the command, biome clean. Changeset: stash minor.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
Addresses the review on #691 — correctness, security, telemetry, and coverage:

- Run the PROJECT-LOCAL drizzle-kit (`pnpm exec` / `npx --no-install`), not the
  `pnpm dlx`/`yarn dlx` download-and-run form, so it resolves the project's own
  drizzle.config.ts + schema. New shared `execCommand`/`execArgv` in init/utils
  (setup-prompt's private copy folded in).
- Invoke via `spawnSync` with an argv array instead of `execSync` on a shell
  string — a `--name` with spaces or shell metacharacters is now one inert token
  (no word-splitting, no command injection). Plus a `[\w-]+` name guard.
- Pass `--out` through to `drizzle-kit generate` (always) so the flag actually
  steers where the migration is written, and our lookup can't miss it.
- Validation exits and every abort throw `CliExit(1)` instead of `process.exit`,
  so the telemetry `finally` runs and the branches are unit-testable.
- Guard `loadBundledEqlSql` (corrupt bundle → clean error, not a fatal trace),
  add the missing `p.intro`, and call `printNextSteps()` so the now-preferred
  install path isn't less helpful than `eql install`.
- HELP banner lists `eql migration`.

Tests: 14 migration unit (target selection incl. `--supabase`-alone, name guard,
dry-run, argv threading, non-zero drizzle-kit, write-failure cleanup, SQL order),
3 `findGeneratedMigration` unit (now public API), e2e smoke + `eql migration
--help`. 557 unit / 65 e2e green, code:check clean.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
@coderdan
coderdan force-pushed the feat/eql-migration-command branch from fd0af48 to 201f7d8 Compare July 18, 2026 12:25
…cast

CodeRabbit review on #691:

- `execArgv`/`execCommand` bun case emitted `bun x`, which auto-installs a
  missing binary from npm — contradicting the "run a project-local binary,
  never surprise-download" contract these helpers exist to honour (the npm case
  already passes `--no-install`). Bun supports `--no-install`; pass it for both
  the argv and shell forms. Updated the setup-prompt assertion to match.
- migration.test.ts: replaced the type-erasing `undefined as unknown as
  writeFileSync` hoisted-mock placeholder with a throwing placeholder cast to the
  specific type — fails loud if the mock factory never runs, and drops the
  `as unknown` (test files are plugin-exempt, but this is cleaner).

CLI build + 566 unit tests green; biome clean.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
Comment thread packages/cli/src/messages.ts Outdated
Comment thread .changeset/eql-migration-command.md Outdated
coderdan added 2 commits July 18, 2026 23:02
…shipped

Dan's review on #691: the `stash eql migration --prisma` copy said the flag
"ships with prisma-next EQL v3 support" / "not available yet" — but that support
has shipped (#655/#683/#685), and Prisma Next installs EQL v3 through its OWN
migration system (`prisma-next migration apply`), not through this command.

Reframed all five spots to current reality (not "coming soon", but "use
prisma-next migration apply"): the user-facing message + its doc comment, the
`eqlMigrationCommand` code comment, the `--prisma` flag help in the registry,
the changeset, and the stash-cli skill (which ships to customers).

No behaviour change — `--prisma` still fails with a pointer. Build + 566 tests green.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…t "use prisma-next")

My previous pass framed `--prisma` as "not supported — use `prisma-next
migration apply`", which contradicts #690: the `--prisma` emitter IS a planned
follow-up (PR3) that writes the install migration in the framework `Migration`
shape and lets prisma-next DROP its baked install baseline. Pointing users at
`prisma-next migration apply` is the very approach #690 replaces.

Reframe all five spots to "not available yet — the Prisma Next emitter is a
follow-up tracked in #690; use `--drizzle` today": the message + its doc comment,
the `eqlMigrationCommand` code comment, the `--prisma` flag help, the changeset,
and the stash-cli skill. (The `stash eql install` guard + stash-prisma-next skill
keep their `prisma-next migration apply` wording — that correctly describes the
current baked-baseline install, which is a different thing from this CLI emitter.)

No behaviour change. Build + 566 tests green.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/commands/db/install.ts (1)

456-503: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Fix OS command injection and migration generation bugs in the v2 path.

While these issues were fixed in the new v3 eql migration command based on previous review feedback, the fixes were not applied to the existing generateDrizzleMigration function used by the v2 eql install --drizzle path:

  1. Command Injection: migrationName is directly interpolated into a shell command and executed via execSync. An attacker-controlled --name (e.g., --name "; rm -rf /") results in arbitrary command execution.
  2. Missing --out argument: The --out argument is never passed to drizzle-kit generate, causing it to write to its default location instead of outDir. This leads to the command failing at step 2 when searching for the file in the wrong directory, thereby leaving behind an orphaned migration.
  3. Incorrect Runner: It uses the download-and-run runnerCommand (e.g., pnpm dlx) instead of running the project-local drizzle-kit via execArgv.

Please apply the same validation and spawnSync implementation used in migration.ts to ensure consistency and security.

Note: The proposed fix below assumes you will update the imports at the top of the file to include spawnSync from node:child_process, as well as execArgv and execCommand from @/commands/init/utils.js.

🛡️ Proposed fix
-  const migrationName = options.name ?? DEFAULT_MIGRATION_NAME
-  const outDir = resolve(options.out ?? DEFAULT_DRIZZLE_OUT)
-  const drizzleCmd = `${runnerCommand(detectPackageManager(), '').trim()} drizzle-kit generate --custom --name=${migrationName}`
+  const migrationName = options.name ?? DEFAULT_MIGRATION_NAME
+  if (!/^[\w-]+$/.test(migrationName)) {
+    p.log.error('Migration name must only contain alphanumeric characters, dashes, and underscores.')
+    process.exit(1)
+  }
+  const outDir = resolve(options.out ?? DEFAULT_DRIZZLE_OUT)
+  const pm = detectPackageManager()
+  const { command, prefixArgs } = execArgv(pm)
+  const drizzleArgs = [
+    ...prefixArgs,
+    'drizzle-kit',
+    'generate',
+    '--custom',
+    `--name=${migrationName}`,
+    `--out=${outDir}`,
+  ]
+  const displayCmd = `${execCommand(pm)} ${drizzleArgs.slice(prefixArgs.length).join(' ')}`

   if (options.dryRun) {
     p.log.info('Dry run — no changes will be made.')
     const source = options.latest
       ? 'Would download EQL install SQL from GitHub'
       : 'Would use bundled EQL install SQL'
     p.note(
-      `Would run: ${drizzleCmd}\n${source}\nWould write SQL to migration file in ${outDir}`,
+      `Would run: ${displayCmd}\n${source}\nWould write SQL to migration file in ${outDir}`,
       'Dry Run',
     )
     p.outro('Dry run complete.')
     return
   }

   let generatedMigrationPath: string | undefined

   // Step 1: Generate a custom Drizzle migration
   s.start('Generating custom Drizzle migration...')

   try {
-    execSync(drizzleCmd, {
+    const result = spawnSync(command, drizzleArgs, {
       stdio: 'pipe',
       encoding: 'utf-8',
     })
+    if (result.status !== 0) {
+      throw new Error(result.stderr?.trim() || result.error?.message || `drizzle-kit exited with status ${result.status ?? 'unknown'}.`)
+    }
     s.stop('Custom Drizzle migration generated.')
   } catch (error) {
     s.stop('Failed to generate migration.')
🤖 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/db/install.ts` around lines 456 - 503, Update
generateDrizzleMigration to match the secure implementation in migration.ts:
validate migrationName, invoke the project-local drizzle-kit through spawnSync
with execArgv and execCommand instead of interpolating it into
execSync/runnerCommand, and pass outDir via the command’s --out argument.
Preserve the existing dry-run and error-reporting behavior while handling the
spawned process result consistently.
🤖 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.

Outside diff comments:
In `@packages/cli/src/commands/db/install.ts`:
- Around line 456-503: Update generateDrizzleMigration to match the secure
implementation in migration.ts: validate migrationName, invoke the project-local
drizzle-kit through spawnSync with execArgv and execCommand instead of
interpolating it into execSync/runnerCommand, and pass outDir via the command’s
--out argument. Preserve the existing dry-run and error-reporting behavior while
handling the spawned process result consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a3145a0-3717-43f8-a6bb-887711605229

📥 Commits

Reviewing files that changed from the base of the PR and between fd0af48 and d1b306f.

📒 Files selected for processing (15)
  • .changeset/eql-migration-command.md
  • packages/cli/src/bin/main.ts
  • packages/cli/src/cli/registry.ts
  • packages/cli/src/commands/db/__tests__/find-generated-migration.test.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/eql/__tests__/migration.test.ts
  • packages/cli/src/commands/eql/migration.ts
  • packages/cli/src/commands/init/lib/__tests__/setup-prompt.test.ts
  • packages/cli/src/commands/init/lib/setup-prompt.ts
  • packages/cli/src/commands/init/utils.ts
  • packages/cli/src/messages.ts
  • packages/cli/tests/e2e/command-help.e2e.test.ts
  • packages/cli/tests/e2e/smoke.e2e.test.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/cli/src/messages.ts
  • packages/cli/tests/e2e/smoke.e2e.test.ts
  • packages/cli/src/commands/db/tests/find-generated-migration.test.ts
  • packages/cli/tests/e2e/command-help.e2e.test.ts
  • packages/cli/src/cli/registry.ts
  • packages/cli/src/commands/init/lib/setup-prompt.ts
  • packages/cli/src/commands/init/utils.ts
  • packages/cli/src/bin/main.ts
  • packages/cli/src/commands/eql/tests/migration.test.ts

@coderdan

Copy link
Copy Markdown
Contributor Author

Human and AI review (coderabbit and Claude/Opus)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant