diff --git a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts index 920c8a20d1d..008e04fcdb4 100644 --- a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts +++ b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts @@ -3,7 +3,7 @@ import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest' import {type UserApplication} from '../helpers' import * as _helpers from '../helpers' -import undeployStudioAction from '../undeployAction' +import undeployStudioAction, {type UndeployStudioActionFlags} from '../undeployAction' // Mock dependencies vi.mock('../helpers') @@ -57,7 +57,7 @@ describe('undeployStudioAction', () => { it('does nothing if there is no user application', async () => { helpers.getUserApplication.mockResolvedValueOnce(null) - await undeployStudioAction({} as CliCommandArguments>, mockContext) + await undeployStudioAction({} as CliCommandArguments, mockContext) expect(mockContext.output.print).toHaveBeenCalledWith( 'Your project has not been assigned a studio hostname', @@ -75,7 +75,7 @@ describe('undeployStudioAction', () => { true, ) // User confirms - await undeployStudioAction({} as CliCommandArguments>, mockContext) + await undeployStudioAction({} as CliCommandArguments, mockContext) expect(mockContext.prompt.single).toHaveBeenCalledWith({ type: 'confirm', @@ -97,7 +97,7 @@ describe('undeployStudioAction', () => { false, ) // User cancels - await undeployStudioAction({} as CliCommandArguments>, mockContext) + await undeployStudioAction({} as CliCommandArguments, mockContext) expect(mockContext.prompt.single).toHaveBeenCalledWith({ type: 'confirm', @@ -107,6 +107,32 @@ describe('undeployStudioAction', () => { expect(helpers.deleteUserApplication).not.toHaveBeenCalled() }) + it(`if running in unattended mode, it doesn't prompt the user for confirmation`, async () => { + helpers.getUserApplication.mockResolvedValueOnce(mockApplication) + helpers.deleteUserApplication.mockResolvedValueOnce(undefined) + ;(mockContext.prompt.single as Mock).mockResolvedValueOnce( + true, + ) // User confirms + + await undeployStudioAction( + {extOptions: {yes: true}} as CliCommandArguments, + mockContext, + ) + + expect(mockContext.prompt.single).not.toHaveBeenCalledWith({ + type: 'confirm', + default: false, + message: expect.stringContaining('undeploy'), + }) + expect(helpers.deleteUserApplication).toHaveBeenCalledWith({ + client: expect.anything(), + applicationId: 'app-id', + }) + expect(mockContext.output.print).toHaveBeenCalledWith( + expect.stringContaining('Studio undeploy scheduled.'), + ) + }) + it('handles errors during the undeploy process', async () => { const errorMessage = 'Example error' helpers.getUserApplication.mockResolvedValueOnce(mockApplication) @@ -116,7 +142,7 @@ describe('undeployStudioAction', () => { ) // User confirms await expect( - undeployStudioAction({} as CliCommandArguments>, mockContext), + undeployStudioAction({} as CliCommandArguments, mockContext), ).rejects.toThrow(errorMessage) expect(mockContext.output.spinner('').fail).toHaveBeenCalled() diff --git a/packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts b/packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts index 376f53b06d4..f1808d30da3 100644 --- a/packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts +++ b/packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts @@ -5,11 +5,17 @@ import {deleteUserApplication, getUserApplication} from './helpers' const debug = debugIt.extend('undeploy') +export interface UndeployStudioActionFlags { + yes?: boolean + y?: boolean +} + export default async function undeployStudioAction( - _: CliCommandArguments>, + args: CliCommandArguments, context: CliCommandContext, ): Promise { const {apiClient, chalk, output, prompt, cliConfig} = context + const flags = args.extOptions const client = apiClient({ requireUser: true, @@ -37,16 +43,25 @@ export default async function undeployStudioAction( output.print('') const url = `https://${chalk.yellow(userApplication.appHost)}.sanity.studio` - const shouldUndeploy = await prompt.single({ - type: 'confirm', - default: false, - message: `This will undeploy ${url} and make it unavailable for your users. + + /** + * Unattended mode means that if there are any prompts it will use `YES` for them but will no change anything that doesn't have a prompt + */ + const unattendedMode = Boolean(flags.yes || flags.y) + + // If it is in unattended mode, we don't want to prompt + if (!unattendedMode) { + const shouldUndeploy = await prompt.single({ + type: 'confirm', + default: false, + message: `This will undeploy ${url} and make it unavailable for your users. The hostname will be available for anyone to claim. Are you ${chalk.red('sure')} you want to undeploy?`.trim(), - }) + }) - if (!shouldUndeploy) { - return + if (!shouldUndeploy) { + return + } } spinner = output.spinner('Undeploying studio').start() diff --git a/packages/sanity/src/_internal/cli/commands/deploy/undeployCommand.ts b/packages/sanity/src/_internal/cli/commands/deploy/undeployCommand.ts index 3864e4ba914..c29a4649bbf 100644 --- a/packages/sanity/src/_internal/cli/commands/deploy/undeployCommand.ts +++ b/packages/sanity/src/_internal/cli/commands/deploy/undeployCommand.ts @@ -4,9 +4,15 @@ import { type CliCommandDefinition, } from '@sanity/cli' +import {type UndeployStudioActionFlags} from '../../actions/deploy/undeployAction' + const helpText = ` +Options + -y, --yes Unattended mode, answers "yes" to any "yes/no" prompt and otherwise uses defaults + Examples sanity undeploy + sanity undeploy --yes ` const undeployCommand: CliCommandDefinition = { @@ -14,7 +20,7 @@ const undeployCommand: CliCommandDefinition = { signature: '', description: 'Removes the deployed Sanity Studio from Sanity hosting', action: async ( - args: CliCommandArguments>, + args: CliCommandArguments, context: CliCommandContext, ) => { const mod = await import('../../actions/deploy/undeployAction')