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 {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,7 @@
it('does nothing if there is no user application', async () => {
helpers.getUserApplication.mockResolvedValueOnce(null)

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

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

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

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

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

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

await expect(

Check failure on line 144 in packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 20)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > handles errors during the undeploy process

AssertionError: expected [Function] to throw error including 'Example error' but got 'Cannot read properties of undefined (…' Expected: "Example error" Received: "Cannot read properties of undefined (reading 'yes')" ❯ actions/deploy/__tests__/undeployAction.test.ts:144:5

Check failure on line 144 in packages/sanity/src/_internal/cli/actions/deploy/__tests__/undeployAction.test.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 18)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > handles errors during the undeploy process

AssertionError: expected [Function] to throw error including 'Example error' but got 'Cannot read properties of undefined (…' Expected: "Example error" Received: "Cannot read properties of undefined (reading 'yes')" ❯ actions/deploy/__tests__/undeployAction.test.ts:144:5
undeployStudioAction({} as CliCommandArguments<Record<string, unknown>>, mockContext),
undeployStudioAction({} 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 @@

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 @@
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)

Check failure on line 50 in packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 20)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > prompts the user for confirmation and undeploys if confirmed

TypeError: Cannot read properties of undefined (reading 'yes') ❯ Module.undeployStudioAction [as default] actions/deploy/undeployAction.ts:50:40 ❯ actions/deploy/__tests__/undeployAction.test.ts:78:5

Check failure on line 50 in packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 20)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > does not undeploy if the user cancels the prompt

TypeError: Cannot read properties of undefined (reading 'yes') ❯ Module.undeployStudioAction [as default] actions/deploy/undeployAction.ts:50:40 ❯ actions/deploy/__tests__/undeployAction.test.ts:100:5

Check failure on line 50 in packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 18)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > prompts the user for confirmation and undeploys if confirmed

TypeError: Cannot read properties of undefined (reading 'yes') ❯ Module.undeployStudioAction [as default] actions/deploy/undeployAction.ts:50:40 ❯ actions/deploy/__tests__/undeployAction.test.ts:78:5

Check failure on line 50 in packages/sanity/src/_internal/cli/actions/deploy/undeployAction.ts

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest / node 18)

actions/deploy/__tests__/undeployAction.test.ts > undeployStudioAction > does not undeploy if the user cancels the prompt

TypeError: Cannot read properties of undefined (reading 'yes') ❯ Module.undeployStudioAction [as default] actions/deploy/undeployAction.ts:50:40 ❯ actions/deploy/__tests__/undeployAction.test.ts:100:5

// If it is in unattended mode, we don't want to prompt
Copy link
Member

Choose a reason for hiding this comment

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

Q: is this comment incorrect, should it be If it is NOT in unattended mode..., or have I misunderstood the logic?

Copy link
Contributor Author

@nkgentile nkgentile Dec 23, 2024

Choose a reason for hiding this comment

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

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