Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): add unattended mode flag to sanity undeploy #8130

Open
wants to merge 2 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -57,7 +57,12 @@ describe('undeployStudioAction', () => {
it('does nothing if there is no user application', async () => {
helpers.getUserApplication.mockResolvedValueOnce(null)

await undeployStudioAction({} as CliCommandArguments<Record<string, unknown>>, mockContext)
await undeployStudioAction(
{
extOptions: {},
} as CliCommandArguments<UndeployStudioActionFlags>,
mockContext,
)

expect(mockContext.output.print).toHaveBeenCalledWith(
'Your project has not been assigned a studio hostname',
Expand All @@ -75,7 +80,12 @@ describe('undeployStudioAction', () => {
true,
) // User confirms

await undeployStudioAction({} as CliCommandArguments<Record<string, unknown>>, mockContext)
await undeployStudioAction(
{
extOptions: {},
} as CliCommandArguments<UndeployStudioActionFlags>,
mockContext,
)

expect(mockContext.prompt.single).toHaveBeenCalledWith({
type: 'confirm',
Expand All @@ -97,7 +107,12 @@ describe('undeployStudioAction', () => {
false,
) // User cancels

await undeployStudioAction({} as CliCommandArguments<Record<string, unknown>>, mockContext)
await undeployStudioAction(
{
extOptions: {},
} as CliCommandArguments<UndeployStudioActionFlags>,
mockContext,
)

expect(mockContext.prompt.single).toHaveBeenCalledWith({
type: 'confirm',
Expand All @@ -107,6 +122,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<typeof mockContext.prompt.single>).mockResolvedValueOnce(
true,
) // User confirms

await undeployStudioAction(
{extOptions: {yes: true}} as CliCommandArguments<UndeployStudioActionFlags>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think you need the same thing in a few of the other failing tests in this suite

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good call––fixed now I believe. I didn't have access to the environment variables to run these locally

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)
Expand All @@ -116,7 +157,12 @@ describe('undeployStudioAction', () => {
) // User confirms

await expect(
undeployStudioAction({} as CliCommandArguments<Record<string, unknown>>, mockContext),
undeployStudioAction(
{
extOptions: {},
} as CliCommandArguments<UndeployStudioActionFlags>,
mockContext,
),
).rejects.toThrow(errorMessage)

expect(mockContext.output.spinner('').fail).toHaveBeenCalled()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<string, unknown>>,
args: CliCommandArguments<UndeployStudioActionFlags>,
context: CliCommandContext,
): Promise<void> {
const {apiClient, chalk, output, prompt, cliConfig} = context
const flags = args.extOptions

const client = apiClient({
requireUser: true,
Expand Down Expand Up @@ -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
jordanl17 marked this conversation as resolved.
Show resolved Hide resolved
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ 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 = {
name: 'undeploy',
signature: '',
description: 'Removes the deployed Sanity Studio from Sanity hosting',
action: async (
args: CliCommandArguments<Record<string, unknown>>,
args: CliCommandArguments<UndeployStudioActionFlags>,
context: CliCommandContext,
) => {
const mod = await import('../../actions/deploy/undeployAction')
Expand Down
Loading