feat: move migration topic into CLI#1322
Conversation
📦 Bundle Stats —
|
| Metric | Value | vs main (987a622) |
|---|---|---|
| Internal (raw) | 2.1 KB | - |
| Internal (gzip) | 799 B | - |
| Bundled (raw) | 11.13 MB | - |
| Bundled (gzip) | 2.10 MB | - |
| Import time | 846ms | -3ms, -0.4% |
bin:sanity
| Metric | Value | vs main (987a622) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.87 MB | - |
| Bundled (gzip) | 1.77 MB | - |
| Import time | 1.96s | +38ms, +2.0% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against main (987a6226)
| Metric | Value | vs main (987a622) |
|---|---|---|
| Internal (raw) | 98.2 KB | - |
| Internal (gzip) | 23.3 KB | - |
| Bundled (raw) | 21.70 MB | - |
| Bundled (gzip) | 3.45 MB | - |
| Import time | 754ms | -1ms, -0.2% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against main (987a6226)
| Metric | Value | vs main (987a622) |
|---|---|---|
| Internal (raw) | 908 B | - |
| Internal (gzip) | 483 B | - |
| Bundled (raw) | 931 B | - |
| Bundled (gzip) | 491 B | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
Coverage Delta
Comparing 11 changed files against main @ Overall Coverage
|
|
Also deferring to @binoy14 here who reviewed the other corresponding PR |
e432ed5 to
ea2ddcc
Compare
📦 Bundle Stats —
|
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 2.2 KB | - |
| Internal (gzip) | 838 B | - |
| Bundled (raw) | 11.16 MB | - |
| Bundled (gzip) | 2.10 MB | - |
| Import time | 869ms | -3ms, -0.3% |
bin:sanity
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.87 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.29s | +34ms, +1.5% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against main (813a9b6f)
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 108.1 KB | - |
| Internal (gzip) | 27.0 KB | - |
| Bundled (raw) | 21.73 MB | - |
| Bundled (gzip) | 3.46 MB | - |
| Import time | 772ms | -3ms, -0.4% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-build
Compared against main (813a9b6f)
@sanity/cli-build/_internal/build
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 113.0 KB | - |
| Internal (gzip) | 28.4 KB | - |
| Bundled (raw) | 18.03 MB | - |
| Bundled (gzip) | 3.62 MB | - |
| Import time | 1.35s | -2ms, -0.1% |
@sanity/cli-build/_internal/env
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 1.8 KB | - |
| Internal (gzip) | 644 B | - |
| Bundled (raw) | 1.31 MB | - |
| Bundled (gzip) | 333.3 KB | - |
| Import time | 127ms | +2ms, +1.6% |
@sanity/cli-build/_internal/extract
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 8.6 KB | - |
| Internal (gzip) | 2.7 KB | - |
| Bundled (raw) | 151.2 KB | - |
| Bundled (gzip) | 38.3 KB | - |
| Import time | 244ms | -1ms, -0.4% |
🗺️ ./_internal/env · ./_internal/extract · @sanity/cli-build:./_internal/build treemap too large to embed · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against main (813a9b6f)
| Metric | Value | vs main (813a9b6) |
|---|---|---|
| Internal (raw) | 908 B | - |
| Internal (gzip) | 483 B | - |
| Bundled (raw) | 931 B | - |
| Bundled (gzip) | 491 B | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…rt error Addresses two open Cursor Bugbot comments on PR #1322: - Wrap the live migration run() in try/finally so the spinner is always stopped, even when run() throws, instead of animating after failure. - Reword the named up/down export error, which previously claimed "up" migrations are supported while rejecting exactly those exports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rt error Addresses two open Cursor Bugbot comments on PR #1322: - Wrap the live migration run() in try/finally so the spinner is always stopped, even when run() throws, instead of animating after failure. - Reword the named up/down export error, which previously claimed "up" migrations are supported while rejecting exactly those exports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b796194 to
50cb6a7
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…igrate plugin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ke test Add boundary assertions verifying the CLI hands @sanity/migrate the full API config (incl. token and apiVersion), concurrency, and the resolved migration. Add an offline smoke test that exercises the real @sanity/migrate dryRun engine against a local export archive, so a runtime contract break (e.g. after a dependency upgrade) is caught even when TypeScript types still line up. This surfaced a latent bug: dryRunHandler was not awaited, so dry-run output could be truncated if the process exits before the async work completes. Await it.
- resolveMigrations: dedupe so a migration id is listed only once, even when multiple loadable candidates resolve or a file and directory share a base name (no more duplicate rows in `migrations list` / run-without-id table) - create: error instead of writing index.ts into the migrations folder itself when the title slugifies to an empty name (e.g. punctuation-only titles) - run: show friendly guidance instead of crashing with ENOENT when invoked without an id and the migrations folder is missing Each fix is covered by a test that fails without it (TDD). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…id-run - run: pass deprecatedFlagName 'project' to getProjectId so `--project` is honored even when sanity.cli.js has no api.projectId. Previously the command failed with "Unable to determine project ID" even when valid --project and --dataset flags were supplied. - run: only finalize the progress spinner (stopAndPersist) once the migration reports done. While running it now updates the spinner text instead of persisting a success line on every in-progress callback, which repeatedly finalized the spinner. Covered by tests (the run.test.ts spinner tests now assert on the spinner mock rather than incidental stderr output). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- run: move the no-id listing branch ahead of the api.projectId/dataset resolution and validation, so `sanity migrations run` (no id) lists available migrations with only a project root, matching `sanity migrations list` - run: surface a clear CLI error when listing fails for a non-ENOENT reason (e.g. a syntax error in a migration file) instead of rethrowing the raw error Both covered by tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rt error Addresses two open Cursor Bugbot comments on PR #1322: - Wrap the live migration run() in try/finally so the spinner is always stopped, even when run() throws, instead of animating after failure. - Reword the named up/down export error, which previously claimed "up" migrations are supported while rejecting exactly those exports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
50cb6a7 to
398c44c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 398c44c. Configure here.
|
|
||
| if ((dataset && !project) || (project && !dataset)) { | ||
| this.error('If either --dataset or --project is provided, both must be provided', {exit: 1}) | ||
| } |
There was a problem hiding this comment.
Config project dataset override blocked
Medium Severity
The pairing check treats a lone --dataset or --project flag as invalid even when the other value is already available from sanity.cli.js via getProjectId or api.dataset. A run like sanity migration run <id> --dataset staging with api.projectId configured fails with “both must be provided” instead of using the configured project.
Reviewed by Cursor Bugbot for commit 398c44c. Configure here.


Description
Currently the
@sanity/migratemodule contains both the migration logic as well as CLI logic. This last part is a bit tedious, especially due to peer dependency version conflicts whenever we release a new major of@sanity/cli-coreetc.This PR moves the CLI logic into the CLI instead of using it as an oclif plugin. This is backwards compatible, but once sanity-io/migrate#92 is released we should upgrade to v8 of that to avoid
@sanity/cli-coredependencies of different versions etc.Note
High Risk
Live
migration run --no-dry-runapplies mutations to configured datasets; regressions in script resolution or migrate v8 integration could cause failed or unintended content changes despite dry-run defaults and confirm prompts.Overview
Content migration commands are now first-class in
@sanity/cliinstead of registering through the@sanity/migrateoclif plugin.oclif.config.jsdrops that plugin (with an explicit note to avoid duplicate command IDs) and adds amigrationstopic;migrationis wired as a topic alias.New surface:
sanity migration create,list, andrunlive undercommands/migrations/, backed byactions/migration/for discovering scripts undermigrations/, scaffolding templates, dry-run mutation formatting, and API version normalization.@sanity/migrateis bumped to ^8.0.0 and used only as the engine (dryRun/run); execution still defaults to dry-run, requires confirmation for live runs, and supports export-based dry runs.Tests cover create/list/run flows, duplicate migration ID resolution, and a smoke test that exercises the real migrate engine against a local export fixture.
Reviewed by Cursor Bugbot for commit 398c44c. Bugbot is set up for automated code reviews on this repo. Configure here.