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

[Themes][app dev][TAE] Find or Create host theme when preparing theme app extension preview #4156

Merged
merged 5 commits into from
Aug 14, 2024
Merged
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
@@ -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
Loading