refactor(deploy): restructure core app deploy and extract shared deploy checks#1406
refactor(deploy): restructure core app deploy and extract shared deploy checks#1406gu-stav wants to merge 8 commits into
Conversation
📦 Bundle Stats —
|
| Metric | Value | vs main (e56340a) |
|---|---|---|
| Internal (raw) | 2.7 KB | - |
| Internal (gzip) | 1.0 KB | - |
| Bundled (raw) | 11.16 MB | - |
| Bundled (gzip) | 2.10 MB | - |
| Import time | 882ms | -8ms, -0.9% |
bin:sanity
| Metric | Value | vs main (e56340a) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.87 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.31s | -7ms, -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 (e56340ae)
| Metric | Value | vs main (e56340a) |
|---|---|---|
| Internal (raw) | 106.7 KB | - |
| Internal (gzip) | 26.7 KB | - |
| Bundled (raw) | 21.72 MB | - |
| Bundled (gzip) | 3.46 MB | - |
| Import time | 790ms | +8ms, +1.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 main (e56340ae)
| Metric | Value | vs main (e56340a) |
|---|---|---|
| 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 8 changed files against main @ Overall Coverage
|
2ba1b26 to
9e824bc
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
…oy checks The app deploy command had grown into one long interleaved function. Restructure it into a single createAppDeployment that validates, prepares and ships, reporting through a shared checks seam, so the command reads as one clear call. This also lands the shared scaffolding the studio deploy will reuse: the checks seam and step helpers (deployChecks), read-only target resolution (resolveDeployTarget), and one home for the auto-update rules (resolveAutoUpdates/getAutoUpdateIssueMessage). Workbench-specific validation moves out of the CLI: viewDeployment goes into @sanity/workbench-cli. Tighten the deploy seams while here: verifyOutputDir takes an isWorkbenchApp boolean instead of a workbench object, checkBuiltOutput becomes a standalone @sanity/workbench-cli/deploy export rather than a method on getWorkbench, and DeployTarget models isExternal as its own field instead of folding it into the type. Adds unit coverage for resolveDeployTarget and resolveTitleUpdate. No behavior change — the integration deploy tests pass unchanged.
9e824bc to
26139a7
Compare
The DeployChecks {add, all, run} interface was hard to follow: each method
behaved differently per mode, and two were no-ops or pass-throughs in the
fail-fast mode that actually runs today (all() returned [], run was identity).
The modes differ in one thing — what a reported check does — so collapse the
seam to a single CheckReporter {report}. A real deploy fails fast
(createFailFastReporter); a dry run collects (createCollectingReporter); the
error-to-fail conversion lives in one shared runStep. verifyOutputDir now
reports through the reporter like every other check, and createAppDeployment
documents the one-sequence-two-modes control flow.
Mirrors the shape of sanity doctor's runDoctorChecks (checks produce results, a
runner decides) as its fail-fast sibling, without sharing code — doctor collects
every check, deploy aborts on the first failure of a dependent sequence.
A build failure now surfaces as "Build failed: <err>" (the formatError that was
dead on the real-deploy path) rather than the generic deploy error.
4efaa12 to
b94a83e
Compare
The view/service deployment payload is validated and logged but never persisted (the storing service doesn't exist yet). Remove it for now; proper view/service handling will be added later.
joshuaellis
left a comment
There was a problem hiding this comment.
Overall looks fine, would be good to get @snocorp's eyes on it as well
Drop the unused DeployCheck.name field (nothing read it) and the checkPackageVersion `name` param it required. Make checkAppTarget report-only (Promise<void>) and delete the DeployTarget descriptor — production never consumed the returned target, only the reported check.
Adds an optional `solution` to DeployCheck and populates it for the blockers with a clear fix (missing package version, build failure, output dir, unresolved app target). The dry-run report renders these under each problem.
snocorp
left a comment
There was a problem hiding this comment.
Overall looks good, I left a couple of suggestions
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 1a67719. Configure here.
`sanity deploy` for a core app could block on a prompt in CI or with --yes: the app-target resolver prompted to pick or name an application, and the custom output-dir overwrite confirm ignored unattended mode. Detect unattended via `isUnattended()` (--yes or a non-TTY terminal), like the rest of the CLI, and error with guidance instead of prompting. Also addresses review feedback: patch @sanity/cli in the changeset, and adopt the vi.mock(import(...)) style for the mocks that spread the original module.
1a67719 to
cb42bf2
Compare
|
Addressed all review comments ✅ One of the other PRs also surfaced I shouldn't look at the This makes it easier, to add |

Description
Note
Lays the foundation for the
--dry-runflag for thedeploycommand. I've created another stacked PR that applies the same refactoring for studios, to keep this one smaller and easier to review.The deploy command had grown into one long function mixing validation, prompting, building, schema upload, packaging and reporting — hard to follow and impossible to reuse for a read-only check.
This is the first of two PRs that restructure it. It covers the core app deploy and lands the shared scaffolding the studio refactor (follow-up) builds on.
deployAppnow runs through a singlecreateAppDeploymentthat validates, prepares and ships the deployment, reporting through a small checks seam. The reusable pieces live in their own modules so the studio path can adopt them next: the checks seam and step helpers, read-only deploy-target resolution, and one home for the auto-update rules. Workbench-specific view validation moves out of the CLI into@sanity/workbench-cli.The checks seam mirrors the
sanity doctorcommand: each step reports a pass/warn/fail outcome to a reporter, and the mode decides what a failure means — a real deploy stops at the first one, a dry run collects them all.Behavior-preserving, with one deliberate exception:
assertDeployableruns before the other checks now, so its "nothing to deploy" error keeps itsUSAGE_ERRORexit code. For an app that's both non-deployable and missing config (sdk-react/org id), that error now surfaces first.What to review
createAppDeploymentagainst the olddeployApp: prompts, spinners, error messages and exit codes should be unchanged.deployChecks,resolveDeployTarget), and that the studio deploy still works on its existing code.Testing
The integration deploy tests pass unchanged. New unit tests cover the checks seam, app target resolution, and the auto-update check mapping.
Note
Medium Risk
Touches the core app deploy path and user-application resolution/API flow; behavior is intended to be preserved but ordering changes (e.g.
assertDeployablefirst) and new unattended errors need careful review.Overview
Refactors core app
sanity deployinto a linearcreateAppDeploymentflow where validation, build, output checks, manifest/title sync, and upload each report through a shared checks seam (CheckReporter: fail-fast for real deploys, collecting for future dry-run).Adds reusable deploy plumbing:
deployChecks(package version, auto-updates, build, output dir, app target), read-onlyresolveAppDeployTarget, and pure auto-update resolution inshouldAutoUpdate(resolveAutoUpdates+ shared messages).findUserApplicationForAppnow maps those verdicts to prompts and supports unattended deploy viaflags.yes(errors instead of select/title prompts). The deploy command propagates unattended mode from--yesor a non-interactive terminal and skips the non-empty output-dir confirm when unattended.Workbench:
checkBuiltOutputis a standalone export from@sanity/workbench-cli/deploy;getWorkbenchkeepsassertDeployableand gainsbuildViewDeploymentPayloadwhile the CLI removes inline view-payload logging from app deploy. Studio deploy only switches to the exportedcheckBuiltOutput. Manifest title sync uses newresolveTitleUpdate.Reviewed by Cursor Bugbot for commit cb42bf2. Bugbot is set up for automated code reviews on this repo. Configure here.