feat(deploy): add --json flag#1416
Conversation
📦 Bundle Stats —
|
| Metric | Value | vs sdk-1784-add-dry-run-support-to-deploy-command (c016b6b) |
|---|---|---|
| Internal (raw) | 2.7 KB | - |
| Internal (gzip) | 1.0 KB | - |
| Bundled (raw) | 11.16 MB | - |
| Bundled (gzip) | 2.10 MB | - |
| Import time | 817ms | -16ms, -1.9% |
bin:sanity
| Metric | Value | vs sdk-1784-add-dry-run-support-to-deploy-command (c016b6b) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.87 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.13s | -45ms, -2.1% |
🗺️ 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 sdk-1784-add-dry-run-support-to-deploy-command (c016b6be)
| Metric | Value | vs sdk-1784-add-dry-run-support-to-deploy-command (c016b6b) |
|---|---|---|
| Internal (raw) | 106.7 KB | - |
| Internal (gzip) | 26.7 KB | - |
| Bundled (raw) | 21.72 MB | - |
| Bundled (gzip) | 3.46 MB | - |
| Import time | 715ms | -22ms, -3.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 — create-sanity
Compared against sdk-1784-add-dry-run-support-to-deploy-command (c016b6be)
| Metric | Value | vs sdk-1784-add-dry-run-support-to-deploy-command (c016b6b) |
|---|---|---|
| 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 10 changed files against main @ Overall Coverage
|
Builds on the core app refactor: deployStudio now runs through a single createStudioDeployment using the shared checks seam, with studio target resolution (resolveStudioDeployTarget) and the studio target check added to the shared modules. With both deploy paths refactored, the two interactive adapters merge into one findUserApplication module and the two creators into createUserApplication, and cannotPromptForStudioHost gets one home. deployStudio carries the tightened seams through: it asks getWorkbench for an isWorkbenchApp boolean, and the studio target models isExternal as its own field. No behavior change — the integration deploy tests pass unchanged.
…cation
The switch's own output.error({exit}) calls were thrown inside the broad
try/catch, which re-wrapped them via output.error(error) with oclif's default
exit 2 — masking the intended exit codes. Scope the try to just the deploy
target fetch (mirroring findUserApplicationForStudio) so intentional exits
surface as thrown.
Both flows now drive a single runDeploy skeleton (fail-fast reporter + one normalized error handler) and read as the same top-to-bottom sequence of reported steps, with the ship/title/schema work pulled into named helpers. Removes the drift between the two deploy paths and their error handling.
Populates `solution` for the remaining blockers: missing organization/project id, external-host misuse, an unresolved studio/app target, and a non-deployable federated app. The dry-run report lists each under its problem.
`sanity deploy --dry-run` reports whether the studio or app can be deployed and lists the files a deploy would upload — without uploading or creating anything. To keep it from drifting, dry-run runs the same createAppDeployment / createStudioDeployment sequence as a real deploy, with a collecting reporter and every network mutation gated off: the target is resolved read-only (checkAppTarget / checkStudioTarget), and the title update, schema upload and deployment upload are skipped. The build still runs locally so the file list is accurate. A new deploymentPlan module owns the plan shape and the human-readable report.
…y-run report When a dry run can't deploy, the report now groups the failing checks under "Problems to fix" — each with its solution — and lists warnings in their own section, instead of a flat mixed list.
b0a6426 to
0222427
Compare
… report The fail-fast reporter now renders a failing (or warning) check's solution beneath its message, so a real deploy is as actionable as the dry-run report. With fixes shown in both modes, the dry-run studio-host problem drops the inline --url hint from its message and carries it in solution instead.
Render the fix on the same line as the problem (`problem: fix`) in both the dry-run report and a real deploy's error, rather than on an indented line.
The dry-run app path resolves the target via checkAppTarget and skipped checkForDeprecatedAppId (which only runs in the real findUserApplication path), so a dry run reported an app as deployable when both app.id and deployment.appId are set — a real deploy exits on that. Share the decision via resolveAppIdIssue and report it as a dry-run check (conflict fails, deprecated-only warns).
A dry run is a non-interactive preview, but a custom non-empty output directory still triggered the confirm prompt, so `sanity deploy --dry-run <dir>` could block waiting for input before the plan ran.
Two dry-run gaps a real deploy doesn't have: - the build (buildApp/buildStudio) could still prompt for prerelease/version choices, so run it unattended in a dry run - a blocking check with a custom exitCode (e.g. USAGE_ERROR) exited 1 instead of the code a real fail-fast deploy uses
Prints the deploy result — or the --dry-run plan — as machine-readable JSON, built from the same data object the human report renders, so the two can't drift. In JSON mode the run's stdout is routed to stderr so sub-steps (build, schema upload) can't corrupt the payload.
Add the resolved sanity/@sanity/sdk-react version as `applicationVersion`, and name the deploy-kind field `applicationType`, so the JSON keys read clearly on their own. Internal plan/spec keep the shorter `type`/`version` names.
…s list Replace the flat `checks` array with `errors` (a map of blocking problem → fix) and `warnings` (messages), dropping informational pass/skip lines. A consumer acts on the fixes directly instead of filtering checks by status.
The "Building to …" line and the overwrite prompt from the custom source-dir path ran before runDeploy installs its stdout redirect, so the JSON payload was no longer the only thing on stdout. Skip both in --json mode.
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 939cb15. Configure here.
|
|
||
| this.output.log(`Building to ${relativeOutput}\n`) | ||
| // Keep stdout clean for --json; this human line would otherwise precede the payload | ||
| if (!flags.json) this.output.log(`Building to ${relativeOutput}\n`) |
There was a problem hiding this comment.
JSON deploy skips unattended flag
Medium Severity
The deploy command treats --json as non-interactive for the output-directory overwrite prompt and the “Building to …” log, but deployFlags only forces yes: true for --dry-run. A real deploy --json run can still hit build prerelease prompts and studio host selection because buildStudio and findUserApplicationForStudio only see flags.yes when the user also passes --yes.
Reviewed by Cursor Bugbot for commit 939cb15. Configure here.
runeb
left a comment
There was a problem hiding this comment.
Adding more json output from cli commands is excellent, and where we want to go. But the bot review points at something I think we need to consider, non-interactive mode. Does it make sense to support interactive prompts when the output requested is json? We could treat json output as non interactive and error early on required params the same way we do -y.
6eb86f5 to
935c7cf
Compare


Description
Note
Stacked on #1415 — adds
--jsonon top of--dry-run.sanity deploy --jsonprints machine-readable JSON instead of the human report, independent of--dry-run:deploy --dry-run --json→ the plan (applicationType,applicationVersion,isDeployable,errors,warnings,files,totalBytes)deploy --json→ the real deploy result (deployed,applicationId,applicationType,applicationVersion, studiolocation)Both the human report and the JSON derive from the same checks, so they can't drift. Blocking problems come back as
errors— a map of problem → fix — withwarningsas a list of messages and a top-levelisDeployableflag, so an agent gets the same "why it can't deploy" the human report shows.In JSON mode the run's stdout is routed to stderr so a sub-step that prints progress straight to stdout (the build, the schema upload) can't corrupt the payload — only the JSON reaches stdout.
Follows the existing
--jsonconvention (doctor,tokens list,schemas list): an explicit flag, no auto-detection.What to review
deployRunner.ts— the JSON branch and theredirectStdoutToStderrguard that keeps stdout clean.deploymentPlanToJsonindeploymentPlan.ts— shares the plan with the human renderer.Testing
Unit covers
deploymentPlanToJson; integration runsdeploy --dry-run --jsonand a realdeploy --json, parsing the payload and asserting no human text leaks to stdout.Note
Low Risk
CLI output and non-interactive behavior only; deploy mutations and API calls are unchanged aside from returning structured metadata after success.
Overview
Adds
sanity deploy --json(-j) so CI and tooling can consume deploy output as JSON instead of the human report.With
--dry-run --json, stdout is a plan derived from the same checks as the text dry-run:applicationType,applicationVersion,isDeployable,errors(message → fix),warnings,files, andtotalBytes. A realdeploy --jsonprints{ deployed: true, applicationId, applicationType, applicationVersion, location }(studio URL ornullfor core apps). Studio and app deploy paths now return a sharedDeployResultfrom their run functions.runDeploybranches on the flag: human mode is unchanged aside from sharedcollectPlan/exitIfBlocked. In JSON mode,process.stdoutis temporarily redirected to stderr so build/schema progress cannot mix with the payload; only the final JSON is logged to stdout. The deploy command also skips overwrite prompts and the “Building to …” line when--jsonis set.deploymentPlanToJsonandisDeployablecentralize the machine-readable dry-run shape; plans include the installed framework version viapackageNameonDeploySpec.Reviewed by Cursor Bugbot for commit 939cb15. Bugbot is set up for automated code reviews on this repo. Configure here.