Skip to content

Commit

Permalink
Merge pull request #4156 from Shopify/jmeng/createHost
Browse files Browse the repository at this point in the history
[Themes][app dev][TAE] Find or Create host theme when preparing theme app extension preview
  • Loading branch information
jamesmengo committed Aug 14, 2024
2 parents 9b0a43c + ce0342a commit 6b3a0ce
Show file tree
Hide file tree
Showing 9 changed files with 530 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {setupPreviewThemeAppExtensionsProcess, findOrCreateHostTheme} from './theme-app-extension-next.js'
import {HostThemeManager} from '../../../utilities/host-theme-manager.js'
import {ExtensionInstance} from '../../../models/extensions/extension-instance.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {AdminSession, ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
import {fetchTheme} from '@shopify/cli-kit/node/themes/api'
import {AbortError} from '@shopify/cli-kit/node/error'
import {Theme} from '@shopify/cli-kit/node/themes/types'
import {vi, describe, test, expect, beforeEach} from 'vitest'

vi.mock('@shopify/cli-kit/node/session')
vi.mock('@shopify/cli-kit/node/themes/api')
vi.mock('../../../utilities/host-theme-manager')
vi.mock('@shopify/cli-kit/node/output')

describe('setupPreviewThemeAppExtensionsProcess', () => {
const mockAdminSession = {storeFqdn: 'test.myshopify.com'} as any as AdminSession
const mockDeveloperPlatformClient = {} as DeveloperPlatformClient
const mockThemeExtension = {isThemeExtension: true} as ExtensionInstance

beforeEach(() => {
vi.mocked(ensureAuthenticatedAdmin).mockResolvedValue(mockAdminSession)
})

test('Returns undefined if no theme extensions are present', async () => {
// Given
const allExtensions: ExtensionInstance[] = []
const storeFqdn = 'test.myshopify.com'
const developerPlatformClient = mockDeveloperPlatformClient

// When
const result = await setupPreviewThemeAppExtensionsProcess({
allExtensions,
storeFqdn,
developerPlatformClient,
})

// Then
expect(result).toBeUndefined()
})

test('Returns PreviewThemeAppExtensionsOptions if theme extensions are present', async () => {
// Given
const mockTheme = {id: 123} as Theme
vi.mocked(fetchTheme).mockResolvedValue(mockTheme)

const allExtensions = [mockThemeExtension]
const storeFqdn = 'test.myshopify.com'
const theme = '123'
const developerPlatformClient = mockDeveloperPlatformClient

// When
const result = await setupPreviewThemeAppExtensionsProcess({
allExtensions,
storeFqdn,
theme,
developerPlatformClient,
})

// Then
expect(result).toBeDefined()
expect(result?.options.themeId).toBe('123')
})
})

describe('findOrCreateHostTheme', () => {
const mockAdminSession = {storeFqdn: 'test.myshopify.com'} as any as AdminSession

test('Returns theme id if theme is provided and found', async () => {
// Given
const mockTheme = {id: 123} as Theme
vi.mocked(fetchTheme).mockResolvedValue(mockTheme)
const theme = '123'

// When
const result = await findOrCreateHostTheme(mockAdminSession, theme)

// Then
expect(result).toBe('123')
expect(HostThemeManager).not.toHaveBeenCalled()
})

test('Throws error if theme is provided and not found', async () => {
// Given
vi.mocked(fetchTheme).mockResolvedValue(undefined)
const theme = '123'

// When
await expect(findOrCreateHostTheme(mockAdminSession, theme)).rejects.toThrow(AbortError)

// Then
expect(HostThemeManager).not.toHaveBeenCalled()
})

test('Returns new theme id if theme is not provided', async () => {
// Given
const mockTheme = {id: 123} as Theme
vi.mocked(HostThemeManager.prototype.findOrCreate).mockResolvedValue(mockTheme)

// When
const result = await findOrCreateHostTheme(mockAdminSession)

// Then
expect(result).toBe('123')
})

test('Throws error if findOrCreateHostTheme fails', async () => {
// Given
vi.mocked(HostThemeManager.prototype.findOrCreate).mockRejectedValue(new Error('error'))

// When
// Then
await expect(findOrCreateHostTheme(mockAdminSession)).rejects.toThrow(Error)
})
})
Original file line number Diff line number Diff line change
@@ -1,49 +1,39 @@
import {BaseProcess, DevProcessFunction} from './types.js'
import {ExtensionInstance} from '../../../models/extensions/extension-instance.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {outputInfo} from '@shopify/cli-kit/node/output'
import {HostThemeManager} from '../../../utilities/host-theme-manager.js'
import {outputDebug, outputInfo} from '@shopify/cli-kit/node/output'
import {AdminSession, ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
import {fetchTheme} from '@shopify/cli-kit/node/themes/api'
import {AbortError} from '@shopify/cli-kit/node/error'
import {Theme} from '@shopify/cli-kit/node/themes/types'
import {renderInfo, renderTasks, Task} from '@shopify/cli-kit/node/ui'

interface PreviewThemeAppExtensionsOptions {
interface ThemeAppExtensionServerOptions {
adminSession: AdminSession
developerPlatformClient: DeveloperPlatformClient
themeId?: string
themeExtensionPort?: number
}

interface HostThemeSetupOptions {
allExtensions: ExtensionInstance[]
storeFqdn: string
theme?: string
themeExtensionPort?: number
developerPlatformClient: DeveloperPlatformClient
}

export interface PreviewThemeAppExtensionsProcess extends BaseProcess<PreviewThemeAppExtensionsOptions> {
export interface PreviewThemeAppExtensionsProcess extends BaseProcess<ThemeAppExtensionServerOptions> {
type: 'theme-app-extensions'
}

const runThemeAppExtensionsServerNext: DevProcessFunction<PreviewThemeAppExtensionsOptions> = async (
{stdout: _stdout, stderr: _stderr, abortSignal: _abortSignal},
{
adminSession: _adminSession,
developerPlatformClient: _developerPlatformClient,
theme: _theme,
themeExtensionPort: _themeExtensionPort,
},
) => {
export async function setupPreviewThemeAppExtensionsProcess(
options: HostThemeSetupOptions,
): Promise<PreviewThemeAppExtensionsProcess | undefined> {
outputInfo('This feature is currently in development and is not ready for use or testing yet.')

await findOrCreateHostTheme()
await initializeFSWatcher()
await startThemeAppExtensionDevelopmentServer()
}

export async function setupPreviewThemeAppExtensionsProcess({
allExtensions,
storeFqdn,
theme,
themeExtensionPort,
developerPlatformClient,
}: Pick<PreviewThemeAppExtensionsOptions, 'developerPlatformClient'> & {
allExtensions: ExtensionInstance[]
storeFqdn: string
theme?: string
themeExtensionPort?: number
}): Promise<PreviewThemeAppExtensionsProcess | undefined> {
outputInfo('This feature is currently in development and is not ready for use or testing yet.')
const {allExtensions, storeFqdn, theme, themeExtensionPort, developerPlatformClient} = options

const themeExtensions = allExtensions.filter((ext) => ext.isThemeExtension)
if (themeExtensions.length === 0) {
Expand All @@ -52,20 +42,61 @@ export async function setupPreviewThemeAppExtensionsProcess({

const adminSession = await ensureAuthenticatedAdmin(storeFqdn)

const themeId = await findOrCreateHostTheme(adminSession, theme)

renderInfo({
headline: {info: 'Setup your theme app extension in the host theme:'},
link: {
label: `https://${adminSession.storeFqdn}/admin/themes/${themeId}/editor`,
url: `https://${adminSession.storeFqdn}/admin/themes/${themeId}/editor`,
},
})

return {
type: 'theme-app-extensions',
prefix: 'theme-extensions',
function: runThemeAppExtensionsServerNext,
options: {
adminSession,
developerPlatformClient,
theme,
themeId,
themeExtensionPort,
},
}
}

async function findOrCreateHostTheme() {}
export async function findOrCreateHostTheme(adminSession: AdminSession, theme?: string): Promise<string> {
let hostTheme: Theme | undefined
if (theme) {
outputDebug(`Fetching theme with provided id ${theme}`)
hostTheme = await fetchTheme(parseInt(theme, 10), adminSession)
} else {
const themeManager = new HostThemeManager(adminSession, {devPreview: true})
const tasks: Task[] = [
{
title: 'Configuring host theme for theme app extension',
task: async () => {
outputDebug('Finding or creating host theme for theme app extensions')
hostTheme = await themeManager.findOrCreate()
},
},
]
await renderTasks(tasks)
}

if (!hostTheme) {
throw new AbortError(`Could not find or create a host theme for theme app extensions`)
}

return hostTheme.id.toString()
}
const runThemeAppExtensionsServerNext: DevProcessFunction<ThemeAppExtensionServerOptions> = async (
{stdout: _stdout, stderr: _stderr, abortSignal: _abortSignal},
_PreviewThemeAppExtensionsOptions,
) => {
await initializeFSWatcher()
await startThemeAppExtensionDevelopmentServer()
}

async function initializeFSWatcher() {}

Expand Down
Loading

0 comments on commit 6b3a0ce

Please sign in to comment.