Skip to content

Commit cdccf1c

Browse files
rexxarsclaude
andcommitted
feat(deploy): add --url flag and unattended mode support
Add --url flag to `sanity deploy` that works for both external and internal studios. For external deploys it passes the full URL directly. For internal deploys it strips the .sanity.studio suffix if present. The flag takes precedence over the studioHost config value. When --yes (unattended mode) is used without --url or studioHost, the command now errors with a helpful message instead of hanging on an interactive prompt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 34577da commit cdccf1c

File tree

5 files changed

+733
-2
lines changed

5 files changed

+733
-2
lines changed

packages/@sanity/cli/src/actions/deploy/deployStudio.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {styleText} from 'node:util'
33
import {createGzip, type Gzip} from 'node:zlib'
44

55
import {CLIError} from '@oclif/core/errors'
6+
import {type Output} from '@sanity/cli-core'
67
import {spinner} from '@sanity/cli-core/ux'
78
import {type StudioManifest} from 'sanity'
89
import {pack} from 'tar-fs'
@@ -21,14 +22,14 @@ import {deployDebug} from './deployDebug.js'
2122
import {deployStudioSchemasAndManifests} from './deployStudioSchemasAndManifests.js'
2223
import {findUserApplicationForStudio} from './findUserApplicationForStudio.js'
2324
import {type DeployAppOptions} from './types.js'
25+
import {normalizeUrl, validateUrl} from './urlUtils.js'
2426

2527
export async function deployStudio(options: DeployAppOptions) {
2628
const {cliConfig, flags, output, projectRoot, sourceDir} = options
2729

2830
const workDir = projectRoot.directory
2931
const configPath = projectRoot.path
3032

31-
const appHost = cliConfig.studioHost
3233
const appId = getAppId(cliConfig)
3334
const projectId = cliConfig.api?.projectId
3435
const installedSanityVersion = await getLocalPackageVersion('sanity', workDir)
@@ -37,6 +38,9 @@ export async function deployStudio(options: DeployAppOptions) {
3738
const isExternal = !!flags.external
3839
const urlType: 'external' | 'internal' = isExternal ? 'external' : 'internal'
3940

41+
// Resolve the app host from --url flag (takes precedence) or studioHost config
42+
const appHost = resolveAppHost({flags, isExternal, output, studioHost: cliConfig.studioHost})
43+
4044
if (!installedSanityVersion) {
4145
output.error(`Failed to find installed sanity version`, {exit: 1})
4246
return
@@ -55,10 +59,22 @@ export async function deployStudio(options: DeployAppOptions) {
5559
appId,
5660
output,
5761
projectId,
62+
unattended: !!flags.yes,
5863
urlType,
5964
})
6065

6166
if (!userApplication) {
67+
if (flags.yes) {
68+
const flagHint = isExternal
69+
? 'Use --url to specify the external studio URL'
70+
: 'Use --url to specify the studio hostname'
71+
output.error(
72+
`Cannot prompt for ${isExternal ? 'external studio URL' : 'studio hostname'} in unattended mode. ${flagHint}.`,
73+
{exit: 1},
74+
)
75+
return
76+
}
77+
6278
if (isExternal) {
6379
output.log('Your project has not been registered with an external studio URL.')
6480
output.log('Please enter the full URL where your studio is hosted.')
@@ -181,3 +197,33 @@ export default defineCliConfig({
181197
output.error(`Error deploying studio: ${error}`, {exit: 1})
182198
}
183199
}
200+
201+
function resolveAppHost({
202+
flags,
203+
isExternal,
204+
output,
205+
studioHost,
206+
}: {
207+
flags: DeployAppOptions['flags']
208+
isExternal: boolean
209+
output: Output
210+
studioHost: string | undefined
211+
}): string | undefined {
212+
const url = flags.url
213+
if (!url) {
214+
return studioHost
215+
}
216+
217+
if (isExternal) {
218+
const normalized = normalizeUrl(url)
219+
const validation = validateUrl(normalized)
220+
if (validation !== true) {
221+
output.error(validation, {exit: 1})
222+
return undefined
223+
}
224+
return normalized
225+
}
226+
227+
// For internal deploys, strip .sanity.studio suffix if present
228+
return url.replace(/\.sanity\.studio\/?$/i, '')
229+
}

packages/@sanity/cli/src/actions/deploy/findUserApplicationForStudio.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ interface FindUserApplicationForStudioOptions {
2020

2121
appHost?: string
2222
appId?: string
23+
unattended?: boolean
2324
urlType?: 'external' | 'internal'
2425
}
2526

2627
export async function findUserApplicationForStudio(options: FindUserApplicationForStudioOptions) {
27-
const {appHost, appId, output, projectId, urlType = 'internal'} = options
28+
const {appHost, appId, output, projectId, unattended = false, urlType = 'internal'} = options
2829

2930
const spin = spinner('Checking project info').start()
3031

@@ -63,6 +64,19 @@ export async function findUserApplicationForStudio(options: FindUserApplicationF
6364
return null
6465
}
6566

67+
// In unattended mode, we can't prompt the user to select a studio
68+
if (unattended) {
69+
const flagHint =
70+
urlType === 'external'
71+
? 'Use --url to specify the external studio URL'
72+
: 'Use --url to specify the studio hostname'
73+
output.error(
74+
`Multiple studios found for this project. Cannot select in unattended mode. ${flagHint}.`,
75+
{exit: 1},
76+
)
77+
return
78+
}
79+
6680
// If there are user applications, allow the user to select one of the existing host names,
6781
// or to create a new one
6882
const newLabel =

0 commit comments

Comments
 (0)