feat(cli): support unattended mode for GraphQL commands#1500
feat(cli): support unattended mode for GraphQL commands#1500jonahsnider wants to merge 8 commits into
Conversation
Signed-off-by: Jonah Snider <jonah@jonahsnider.com>
📦 Bundle Stats —
|
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 2.2 KB | - |
| Internal (gzip) | 838 B | - |
| Bundled (raw) | 11.20 MB | +36.8 KB, +0.3% |
| Bundled (gzip) | 2.11 MB | +6.8 KB, +0.3% |
| Import time | 881ms | -11ms, -1.2% |
bin:sanity
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.90 MB | +33.7 KB, +0.3% |
| Bundled (gzip) | 1.78 MB | +6.3 KB, +0.3% |
| Import time | 2.31s | +8ms, +0.3% |
🗺️ 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 (abece333)
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 113.4 KB | +5.3 KB, +4.9% |
| Internal (gzip) | 29.0 KB | +2.0 KB, +7.5% |
| Bundled (raw) | 21.76 MB | +38.9 KB, +0.2% |
| Bundled (gzip) | 3.46 MB | +7.9 KB, +0.2% |
| Import time | 783ms | -5ms, -0.6% |
🗺️ 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 (abece333)
@sanity/cli-build/_internal/build
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 113.8 KB | +2.9 KB, +2.6% |
| Internal (gzip) | 28.7 KB | +788 B, +2.8% |
| Bundled (raw) | 18.10 MB | +166.2 KB, +0.9% |
| Bundled (gzip) | 3.64 MB | +40.1 KB, +1.1% |
| Import time | 1.38s | +16ms, +1.1% |
@sanity/cli-build/_internal/env
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 1.8 KB | - |
| Internal (gzip) | 644 B | - |
| Bundled (raw) | 1.31 MB | +2.9 KB, +0.2% |
| Bundled (gzip) | 333.8 KB | +896 B, +0.3% |
| Import time | 126ms | -2ms, -1.7% |
@sanity/cli-build/_internal/extract
| Metric | Value | vs main (abece33) |
|---|---|---|
| Internal (raw) | 8.6 KB | - |
| Internal (gzip) | 2.7 KB | - |
| Bundled (raw) | 155.0 KB | +3.9 KB, +2.6% |
| Bundled (gzip) | 39.5 KB | +1.2 KB, +3.0% |
| Import time | 250ms | +1ms, +0.5% |
🗺️ ./_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 (abece333)
| Metric | Value | vs main (abece33) |
|---|---|---|
| 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 4 changed files against main @ Overall Coverage
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b2d6d24. Configure here.
| message: 'Are you sure you want to deploy?', | ||
| })) | ||
|
|
||
| return confirmDeploy ? specifiedGeneration : undefined |
There was a problem hiding this comment.
nit, there's a lot of ternary statements in this codebase, interesting that the agent decided to refactor this
| // Confirm deletion unless --force is used | ||
| if (!force) { | ||
| if (this.isUnattended()) { | ||
| this.error('GraphQL API undeploy requires confirmation. Pass --force to continue.', { |
There was a problem hiding this comment.
curious if there's an acceptable subset of commands that we wouldn't want to expose in unattended mode? seeing --force surfaced as an escape hatch for a destructive operation makes sense in human mode, i'm wondering about whether we expect the agent harness (and human who owns it) to be responsible for deciding when to take an action like this
|
|
||
| if (!yes && this.isUnattended()) { | ||
| this.error( | ||
| 'Schema deletion requires confirmation. Re-run with --yes to delete the schemas.', |
There was a problem hiding this comment.
in re my earlier comment on unattended destructive actions, this messaging is great, might be worth porting it to other destructive op sites
|
|
||
| if (!shouldOverwrite) { | ||
| this.output.log('Schema extraction cancelled') | ||
| return this.exit(exitCodes.USER_ABORT) |
There was a problem hiding this comment.
super nit / fast follow candidate, if the exitCodes set includes semantics around 1 | 2 | 3, might be worth a pass to refactor all the places we currently supply a bare integer
There was a problem hiding this comment.
yeah i don't really know why there's a mix of constants and magic numbers. i can't imagine there's a good reason. i'll standardize on the constants in my changes and come back later to update the rest
shapirodaniel
left a comment
There was a problem hiding this comment.
a few high level qs, otherwise 🚢

Description
Adds unattended-mode support for GraphQL commands.
What to review
Review the flags, prompt handling, and automated coverage.
Testing
Note
Medium Risk
Changes confirmation and exit-code behavior for deploy, undeploy, and schema delete/extract—scripts relying on old exit codes or silent skips may need updates, though defaults in interactive use are largely preserved.
Overview
Adds unattended-mode behavior across GraphQL deploy/undeploy and experimental schema commands so CI and scripted runs do not hang on prompts.
GraphQL: Deploy and undeploy now use
isUnattended()instead of rawisInteractive(). In unattended mode, destructive or ambiguous steps require--force(generation mismatch, breaking changes, multi-API flag overrides, undeploy). User declines and cancellations exit withUSER_ABORTand clearer messages instead of generic “Operation cancelled”. Missing dataset / usage mistakes useUSAGE_ERROR.Schema:
schema deletegains--yesand blocks unattended deletes without it, with an interactive confirm otherwise.schema extractgains--forceto overwrite an existing output file; unattended runs error without it.schema list --jsonroutes fetch/permission diagnostics to stderr (warn) so stdout stays valid JSON.Tests are aligned with explicit
isInteractivemocks and the new exit codes.Reviewed by Cursor Bugbot for commit c2bd3f1. Bugbot is set up for automated code reviews on this repo. Configure here.