diff --git a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/deployAction.test.ts b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/deployAction.test.ts index fd0007831dc..493ad21f0cf 100644 --- a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/deployAction.test.ts +++ b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/deployAction.test.ts @@ -16,7 +16,7 @@ jest.mock('../helpers') jest.mock('../../build/buildAction') type Helpers = typeof _helpers -const helpers = _helpers as {[K in keyof Helpers]: jest.Mock} +const helpers = _helpers as unknown as {[K in keyof Helpers]: jest.Mock} const buildSanityStudioMock = buildSanityStudio as jest.Mock const tarPackMock = tar.pack as jest.Mock const zlibCreateGzipMock = zlib.createGzip as jest.Mock @@ -118,6 +118,60 @@ describe('deployStudioAction', () => { expect(mockSpinner.succeed).toHaveBeenCalled() }) + it('builds and deploys the studio if the directory is empty and hostname in config', async () => { + const mockSpinner = mockContext.output.spinner('') + mockContext.cliConfig = {studioHost: 'app-host'} + + // Mock utility functions + helpers.dirIsEmptyOrNonExistent.mockResolvedValueOnce(true) + helpers.getInstalledSanityVersion.mockResolvedValueOnce('vX') + helpers.getOrCreateUserApplicationFromConfig.mockResolvedValueOnce(mockApplication) + helpers.createDeployment.mockResolvedValueOnce({location: 'https://app-host.sanity.studio'}) + buildSanityStudioMock.mockResolvedValueOnce({didCompile: true}) + tarPackMock.mockReturnValueOnce({pipe: jest.fn().mockReturnValue('tarball')}) + zlibCreateGzipMock.mockReturnValue('gzipped') + + await deployStudioAction( + { + argsWithoutOptions: ['customSourceDir'], + extOptions: {}, + } as CliCommandArguments, + mockContext, + ) + + // Check that buildSanityStudio was called + expect(buildSanityStudioMock).toHaveBeenCalledWith( + expect.objectContaining({ + extOptions: {build: true}, + argsWithoutOptions: ['customSourceDir'], + }), + mockContext, + {basePath: '/'}, + ) + expect(helpers.dirIsEmptyOrNonExistent).toHaveBeenCalledWith( + expect.stringContaining('customSourceDir'), + ) + expect(helpers.getOrCreateUserApplicationFromConfig).toHaveBeenCalledWith( + expect.objectContaining({ + client: expect.anything(), + context: expect.anything(), + appHost: 'app-host', + }), + ) + expect(helpers.createDeployment).toHaveBeenCalledWith({ + client: expect.anything(), + applicationId: 'app-id', + version: 'vX', + isAutoUpdating: false, + tarball: 'tarball', + }) + + expect(mockContext.output.print).toHaveBeenCalledWith( + '\nSuccess! Studio deployed to https://app-host.sanity.studio', + ) + expect(mockSpinner.succeed).toHaveBeenCalled() + }) + it('prompts the user if the directory is not empty', async () => { const mockSpinner = mockContext.output.spinner('') @@ -205,8 +259,6 @@ describe('deployStudioAction', () => { }) it('returns an error if API responds with 402', async () => { - const mockSpinner = mockContext.output.spinner('') - // Mock utility functions helpers.dirIsEmptyOrNonExistent.mockResolvedValueOnce(true) helpers.getInstalledSanityVersion.mockResolvedValueOnce('vX') diff --git a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/helpers.test.ts b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/helpers.test.ts index 8ed47e3645b..c634816e96a 100644 --- a/packages/sanity/src/_internal/cli/actions/deploy/__tests__/helpers.test.ts +++ b/packages/sanity/src/_internal/cli/actions/deploy/__tests__/helpers.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import {type Stats} from 'node:fs' import fs from 'node:fs/promises' import {Readable} from 'node:stream' @@ -13,6 +14,7 @@ import { deleteUserApplication, dirIsEmptyOrNonExistent, getOrCreateUserApplication, + getOrCreateUserApplicationFromConfig, } from '../helpers' jest.mock('node:fs/promises') @@ -36,8 +38,12 @@ const mockOutput = { warn: jest.fn(), spinner: jest.fn(), } as CliCommandContext['output'] -const mockPrompt = {single: jest.fn()} as unknown as CliCommandContext['prompt'] +const mockPrompt = { + single: jest.fn(), + Separator: jest.fn(), +} as unknown as CliCommandContext['prompt'] const mockSpinner = { + start: jest.fn(), succeed: jest.fn(), } as unknown as ReturnType @@ -68,6 +74,71 @@ describe('getOrCreateUserApplication', () => { expect(result).toEqual({id: 'default-app'}) }) + it('creates a user application by prompting the user for a name', async () => { + const newApp = { + id: 'default-app', + appHost: 'default.sanity.studio', + urlType: 'internal', + } + mockClientRequest.mockResolvedValueOnce(null) // Simulate no existing app + mockClientRequest.mockResolvedValueOnce([]) // Simulate no list of deployments + ;(mockPrompt.single as jest.Mock).mockImplementationOnce( + async ({validate}: Parameters[0]) => { + // Simulate user input and validation + const appHost = 'default.sanity.studio' + await validate!(appHost) + return appHost + }, + ) + mockClientRequest.mockResolvedValueOnce(newApp) + + const result = await getOrCreateUserApplication({ + client: mockClient, + context, + spinner: mockSpinner, + }) + + expect(mockPrompt.single).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Studio hostname (.sanity.studio):', + }), + ) + expect(result).toEqual(newApp) + }) + + it('allows user to select a user application from a list', async () => { + const existingApp = { + id: 'default-app', + appHost: 'default.sanity.studio', + urlType: 'internal', + } + mockClientRequest.mockResolvedValueOnce(null) // Simulate no existing app + mockClientRequest.mockResolvedValueOnce([existingApp]) // Simulate no list of deployments + ;(mockPrompt.single as jest.Mock).mockImplementationOnce(async ({choices}: any) => { + // Simulate user input + return Promise.resolve(choices[2].value) + }) + + const result = await getOrCreateUserApplication({ + client: mockClient, + context, + spinner: mockSpinner, + }) + + expect(mockPrompt.single).toHaveBeenCalledWith( + expect.objectContaining({ + choices: expect.arrayContaining([expect.objectContaining({name: 'default.sanity.studio'})]), + }), + ) + expect(result).toEqual(existingApp) + }) +}) + +describe('getOrCreateUserApplicationFromConfig', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + it('gets an existing user application if a `studioHost` is provided in the config', async () => { mockClientRequest.mockResolvedValueOnce({ id: 'existing-app', @@ -75,16 +146,16 @@ describe('getOrCreateUserApplication', () => { urlType: 'internal', }) - const result = await getOrCreateUserApplication({ + const result = await getOrCreateUserApplicationFromConfig({ client: mockClient, spinner: mockSpinner, - cliConfig: {studioHost: 'example.sanity.studio'}, context, + appHost: 'example', }) expect(mockClientRequest).toHaveBeenCalledWith({ uri: '/user-applications', - query: {appHost: 'example.sanity.studio'}, + query: {appHost: 'example'}, }) expect(result).toEqual({ id: 'existing-app', @@ -102,11 +173,11 @@ describe('getOrCreateUserApplication', () => { mockClientRequest.mockResolvedValueOnce(null) // Simulate no existing app mockClientRequest.mockResolvedValueOnce(newApp) - const result = await getOrCreateUserApplication({ + const result = await getOrCreateUserApplicationFromConfig({ client: mockClient, spinner: mockSpinner, - cliConfig: {studioHost: 'newhost'}, context, + appHost: 'newhost', }) expect(mockClientRequest).toHaveBeenCalledTimes(2) @@ -121,37 +192,6 @@ describe('getOrCreateUserApplication', () => { }) expect(result).toEqual(newApp) }) - - it('creates a user application by prompting the user for a name', async () => { - const newApp = { - id: 'default-app', - appHost: 'default.sanity.studio', - urlType: 'internal', - } - mockClientRequest.mockResolvedValueOnce(null) // Simulate no existing app - ;(mockPrompt.single as jest.Mock).mockImplementationOnce( - async ({validate}: Parameters[0]) => { - // Simulate user input and validation - const appHost = 'default.sanity.studio' - await validate!(appHost) - return appHost - }, - ) - mockClientRequest.mockResolvedValueOnce(newApp) - - const result = await getOrCreateUserApplication({ - client: mockClient, - context, - spinner: mockSpinner, - }) - - expect(mockPrompt.single).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Studio hostname (.sanity.studio):', - }), - ) - expect(result).toEqual(newApp) - }) }) describe('createDeployment', () => {