Skip to content

feat: allow selecting app/studio to undeploy#625

Open
rexxars wants to merge 9 commits intomainfrom
feat/undeploy-prompt-for-app
Open

feat: allow selecting app/studio to undeploy#625
rexxars wants to merge 9 commits intomainfrom
feat/undeploy-prompt-for-app

Conversation

@rexxars
Copy link
Copy Markdown
Member

@rexxars rexxars commented Mar 12, 2026

Description

Not urgent, new behavior.

Sort of annoying that if you deploy a studio/app and don't remember/see to put the appId into your CLI config, undeploy just… doesn't work. Now, if there's no appId specified, we allow the user to select from a list of apps. If it detects that you're in an app folder, it will give you app choices. If you're in a studio folder, it will give you studios.

Prompts before actually undeploying.

What to review

Do the flows make sense?

Flowchart:

sanity undeploy
  │
  ├─ determineIsApp(cliConfig)
  │  ├─ isApp = true   (has app config)
  │  └─ isApp = false  (studio)
  │
  ▼
  resolveApplication(cliConfig, isApp)
  │
  ├─── APP PATH (isApp = true) ──────────────────────────────────────────
  │    │
  │    ├─ getAppId(cliConfig) → has appId?
  │    │  │
  │    │  ├─ YES → getUserApplication({appId, isSdkApp: true})
  │    │  │        ├─ found    → return it ✓
  │    │  │        ├─ 404      → "does not exist" → exit
  │    │  │        └─ error    → fail with error message
  │    │  │
  │    │  └─ NO → is unattended?
  │    │         ├─ YES → "No application ID provided" → exit
  │    │         └─ NO  → promptForApp()
  │    │                  │
  │    │                  ├─ no organizationId? → info "Cannot list" → exit
  │    │                  ├─ API error?         → fail with error
  │    │                  ├─ empty list?        → info "No deployed apps" → exit
  │    │                  └─ has apps           → select prompt → return selected
  │
  ├─── STUDIO PATH (isApp = false) ──────────────────────────────────────
  │    │
  │    ├─ has studioHost or deployment.appId?
  │    │  │
  │    │  ├─ YES → need projectId
  │    │  │        ├─ no projectId → error(NO_PROJECT_ID)
  │    │  │        └─ has projectId → getUserApplication({appHost, appId, projectId})
  │    │  │                           ├─ found    → return it ✓
  │    │  │                           ├─ 404      → "does not exist" → exit
  │    │  │                           └─ error    → fail with error message
  │    │  │
  │    │  └─ NO → is unattended?
  │    │         ├─ YES → "No application ID or studio host" → exit
  │    │         └─ NO  → promptForStudio()
  │    │                  │
  │    │                  ├─ no projectId? → info "Cannot list" → exit
  │    │                  ├─ API error?    → fail with error
  │    │                  ├─ empty list?   → info "No deployed studios" → exit
  │    │                  └─ has studios   → select prompt → return selected
  │
  ▼
  userApplication resolved (or exit)
  │
  ├─ is unattended? → skip confirmation
  ├─ interactive?   → confirmUndeploy() → user says no? → exit
  │
  ▼
  deleteUserApplication()
  ├─ error → fail with error
  └─ success
     │
     ├─ "Studio/Application undeploy scheduled."
     │
     ├─ has deployment.appId? → "Remember to remove `deployment.appId`"
     └─ has app.id?           → "Remember to remove `app.id`"

Testing

Added new tests for the new behavior

@claude
Copy link
Copy Markdown

claude Bot commented Mar 12, 2026

Claude finished @binoy14's task in 4m 33s —— View job


Review

  • Read changed files
  • Analyze for issues
  • Post review

userApplications.ts:59-61 — Dead else branch returns wrong type

} else if (appHost) {
  uri = `/projects/${projectId}/user-applications`
  query = {appHost, appType: 'studio'}
} else {
  uri = `/projects/${projectId}/user-applications`  // ← no query params, no appType
}

When isSdkApp=false and neither appId nor appHost is provided, this hits a bare list endpoint that returns UserApplication[], but the function is typed to return UserApplication | null. This branch is currently unreachable (callers in resolveApplication guard with cliConfig.studioHost || cliConfig.deployment?.appId before calling), but it's a latent type confusion. Should either throw an error or be removed entirely.


undeploy.ts:137,185 — "Nothing to undeploy." missing in two early-exit paths

promptForApp (no organizationId) and promptForStudio (no projectId) call spin.info(...) and return undefined, but never print "Nothing to undeploy.". All other early-exit paths (empty list, unattended mode) do print it. The inconsistency won't confuse a user badly, but the test for this case also doesn't assert the absence of that message so it could go unnoticed.

Fix this →

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

Coverage Delta

File Statements
packages/@sanity/cli/src/commands/undeploy.ts 100.0% (±0%)
packages/@sanity/cli/src/services/userApplications.ts 98.1% (+ 5.3%)

Comparing 2 changed files against main @ 2f44698e5e319fc69d5266b00ac7f50026011c13

Overall Coverage

Metric Coverage
Statements 81.6% (+ 0.1%)
Branches 70.2% (+ 0.1%)
Functions 79.8% (+ 0.1%)
Lines 82.0% (+ 0.1%)

@rexxars rexxars force-pushed the feat/undeploy-prompt-for-app branch from 4c751af to 7f352e7 Compare March 12, 2026 18:48
@rexxars rexxars marked this pull request as ready for review March 12, 2026 20:38
@rexxars rexxars requested a review from a team as a code owner March 12, 2026 20:38
@rexxars rexxars requested review from laurenashpole and removed request for a team March 12, 2026 20:38
rexxars and others added 9 commits March 30, 2026 15:19
- Wrap deleteUserApplication in try/catch so spinner fails properly on error
- Add test for delete API failure (500 response)
- Add missing 'Remember to remove' assertion on prioritizes deployment.appId test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Narrow `err` to `Error | string` in promptForApplication catch block
- Distinguish null (API error) from empty array in getUserApplications
- Fix misleading "not been assigned" message when configured host doesn't exist
- Add test for app listing API failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove explicit {exit: 1} from app listing error to default to 2,
  matching the studio path behavior
- Add test for studio listing API failure in interactive mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove double error handling in promptForApplication by delegating
directly to promptForApp/promptForStudio. Add try/catch in both prompt
methods for consistent error handling and fix variable naming bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use spin.info() instead of spin.fail() for empty app/studio lists
  since an empty result is not an error
- Remove error swallowing in getUserApplications for coreApp so server
  errors propagate with their original message
- Fix implementation return type to match overloads (no more | null)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use getAppId() for the "remember to remove" check so it also triggers
  for the deprecated app.id config path
- Remove redundant this.parse() call since init() already populates flags
- Add explicit assertions to the rejection-after-selection test
- Add test for deprecated app.id showing the reminder message

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show the right config key in the reminder message depending on whether
  deployment.appId or the deprecated app.id is set
- Remove unreachable {default: 'true'} fallback in getUserApplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix type error from removing ternary in getUserApplication by using
  proper branching for appHost
- Remove dead isSdkApp URI fallback (appId is required by the type)
- Use spin.info() consistently for missing config (orgId, projectId)
  instead of spin.fail() + this.log()
- Add test for studio deployment.appId with missing projectId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@binoy14 binoy14 force-pushed the feat/undeploy-prompt-for-app branch from 1dcda15 to e65f853 Compare March 30, 2026 19:19
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

📦 Bundle Stats — @sanity/cli

Compared against main (c37431d6) · v6.2.1 (npm)

@sanity/cli

Metric Value vs main (c37431d) vs v6.2.1
Internal (raw) 2.1 KB - -
Internal (gzip) 799 B - -
Bundled (raw) 11.06 MB - +9.12 MB, +470.6%
Bundled (gzip) 2.07 MB - +1.60 MB, +335.8%
Import time 863ms +5ms, +0.6% +34ms, +4.1%

bin:sanity

Metric Value vs main (c37431d) vs v6.2.1
Internal (raw) 975 B - -
Internal (gzip) 460 B - -
Bundled (raw) 9.83 MB - +9.12 MB, +1286.7%
Bundled (gzip) 1.77 MB - +1.60 MB, +940.0%
Import time NaNs - NaNs, NaN%

🗺️ 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 (c37431d6) · v1.2.1 (npm)

Metric Value vs main (c37431d) vs v1.2.1
Internal (raw) 92.2 KB - +3.9 KB, +4.4%
Internal (gzip) 21.6 KB - +1.1 KB, +5.5%
Bundled (raw) 21.72 MB - +9.16 MB, +72.9%
Bundled (gzip) 3.45 MB - +1.61 MB, +87.2%
Import time 816ms +7ms, +0.8% +54ms, +7.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.

@github-actions
Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/commands/undeploy.ts 100.0% (±0%)
packages/@sanity/cli/src/services/userApplications.ts 98.1% (+ 5.3%)

Comparing 2 changed files against main @ c37431d667a9edccd74f2435021eed7ea0e0388f

Overall Coverage

Metric Coverage
Statements 83.2% (+ 0.1%)
Branches 73.0% (+ 0.1%)
Functions 83.4% (+ 0.1%)
Lines 83.6% (+ 0.1%)

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