Skip to content

Commit

Permalink
Add shortcut keys to theme dev
Browse files Browse the repository at this point in the history
This commit adds shortcut keys to the theme dev CLI service. The keys are as follows:
e - open theme editor
t - preview your theme locally
p - preview your theme (share)
g - preview gift cards
  • Loading branch information
EvilGenius13 committed Nov 21, 2024
1 parent 2450a80 commit 45d6424
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-frogs-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme': minor
---

Add shortcut keys to theme dev commands
118 changes: 81 additions & 37 deletions packages/theme/src/cli/services/dev.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {dev, DevOptions, renderLinks} from './dev.js'
import {dev, DevOptions, openURLSafely, renderLinks} from './dev.js'
import {setupDevServer} from '../utilities/theme-environment/theme-environment.js'
import {mountThemeFileSystem} from '../utilities/theme-fs.js'
import {fakeThemeFileSystem} from '../utilities/theme-fs/theme-fs-mock-factory.js'
Expand All @@ -11,7 +11,8 @@ import {describe, expect, test, vi} from 'vitest'
import {buildTheme} from '@shopify/cli-kit/node/themes/factories'
import {DEVELOPMENT_THEME_ROLE} from '@shopify/cli-kit/node/themes/utils'
import {fetchChecksums} from '@shopify/cli-kit/node/themes/api'
import {renderSuccess} from '@shopify/cli-kit/node/ui'
import {renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui'
import {openURL} from '@shopify/cli-kit/node/system'

vi.mock('@shopify/cli-kit/node/ui')
vi.mock('@shopify/cli-kit/node/themes/api')
Expand All @@ -21,6 +22,16 @@ vi.mock('../utilities/theme-environment/storefront-session.js')
vi.mock('../utilities/theme-environment/theme-environment.js')
vi.mock('../utilities/theme-fs-empty.js')
vi.mock('../utilities/theme-fs.js')
vi.mock('@shopify/cli-kit/node/colors', () => ({
default: {
bold: (str: string) => str,
cyan: (str: string) => str,
gray: (str: string) => str,
},
}))
vi.mock('@shopify/cli-kit/node/system', () => ({
openURL: vi.fn(),
}))

describe('dev', () => {
const store = 'my-store.myshopify.com'
Expand Down Expand Up @@ -95,50 +106,83 @@ describe('dev', () => {
})
})

describe('renderLinks', async () => {
test('renders "dev" command links', async () => {
// Given
const themeId = theme.id.toString()
test('renders "dev" command links', async () => {
// Given
const themeId = theme.id.toString()
const host = '127.0.0.1'
const port = '9292'
const urls = {
local: `http://${host}:${port}`,
giftCard: `http://${host}:${port}/gift_cards/[store_id]/preview`,
themeEditor: `https://${store}/admin/themes/${themeId}/editor`,
preview: `https://${store}/?preview_theme_id=${themeId}`,
}

// When
renderLinks(store, themeId)
// When
renderLinks(urls)

// Then
expect(renderSuccess).toHaveBeenCalledWith({
body: [
{
list: {
items: ['http://127.0.0.1:9292'],
title: {
bold: 'Preview your theme',
// Then
expect(renderSuccess).toHaveBeenCalledWith({
body: [
{
list: {
title: 'Preview your theme (t)',
items: [
{
link: {
url: 'http://127.0.0.1:9292',
},
},
],
},
},
],
nextSteps: [
[
{
link: {
label: `Share your theme preview (p)`,
url: `https://${store}/?preview_theme_id=${themeId}`,
},
},
{
subdued: `https://${store}/?preview_theme_id=${themeId}`,
},
],
nextSteps: [
[
{
link: {
label: 'Preview your gift cards',
url: 'http://127.0.0.1:9292/gift_cards/[store_id]/preview',
},
},
],
[
{
link: {
label: 'Customize your theme at the theme editor',
url: 'https://my-store.myshopify.com/admin/themes/123/editor',
},
[
{
link: {
label: `Customize your theme at the theme editor (e)`,
url: `https://${store}/admin/themes/${themeId}/editor`,
},
],
[
'Share your theme preview',
{
subdued: '\nhttps://my-store.myshopify.com/?preview_theme_id=123',
},
],
[
{
link: {
label: 'Preview your gift cards (g)',
url: 'http://127.0.0.1:9292/gift_cards/[store_id]/preview',
},
],
},
],
],
})
})
describe('openURLSafely', () => {
test('calls renderWarning when openURL fails', async () => {
// Given
const error = new Error('Failed to open URL')
vi.mocked(openURL).mockRejectedValueOnce(error)

// When
openURLSafely('http://127.0.0.1:9292', 'localhost')

// Then
await vi.waitFor(() => {
expect(renderWarning).toHaveBeenCalledWith({
headline: 'Failed to open localhost.',
body: error.stack ?? error.message,
})
})
})
})
Expand Down
83 changes: 69 additions & 14 deletions packages/theme/src/cli/services/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {Theme} from '@shopify/cli-kit/node/themes/types'
import {checkPortAvailability, getAvailableTCPPort} from '@shopify/cli-kit/node/tcp'
import {AbortError} from '@shopify/cli-kit/node/error'
import {openURL} from '@shopify/cli-kit/node/system'
import chalk from '@shopify/cli-kit/node/colors'
import readline from 'readline'

const DEFAULT_HOST = '127.0.0.1'
const DEFAULT_PORT = '9292'
Expand Down Expand Up @@ -61,6 +63,13 @@ export async function dev(options: DevOptions) {

const port = options.port ?? String(await getAvailableTCPPort(Number(DEFAULT_PORT)))

const urls = {
local: `http://${host}:${port}`,
giftCard: `http://${host}:${port}/gift_cards/[store_id]/preview`,
themeEditor: `https://${options.store}/admin/themes/${options.theme.id}/editor`,
preview: `https://${options.store}/?preview_theme_id=${options.theme.id}`,
}

const storefrontPassword = await storefrontPasswordPromise
const session = await initializeDevServerSession(
options.theme.id.toString(),
Expand Down Expand Up @@ -98,47 +107,93 @@ export async function dev(options: DevOptions) {
await renderDevSetupProgress()
await serverStart()

renderLinks(options.store, String(options.theme.id), host, port)
renderLinks(urls)
if (options.open) {
openURL(`http://${host}:${port}`).catch((error: Error) => {
renderWarning({headline: 'Failed to open the development server.', body: error.stack ?? error.message})
openURLSafely(urls.local, 'development server')
}

readline.emitKeypressEvents(process.stdin)
if (process.stdin.isTTY) {
process.stdin.setRawMode(true)
}

process.stdin.on('keypress', (_str, key) => {
if (key.ctrl && key.name === 'c') {
process.exit()
}

switch (key.name) {
case 't':
openURLSafely(urls.local, 'localhost')
break
case 'p':
openURLSafely(urls.preview, 'theme preview')
break
case 'e':
openURLSafely(urls.themeEditor, 'theme editor')
break
case 'g':
openURLSafely(urls.giftCard, 'gift card preview')
break
}
})
}

export function openURLSafely(url: string, label: string) {
openURL(url).catch(handleOpenURLError(label))
}

function handleOpenURLError(message: string) {
return (error: Error) => {
renderWarning({
headline: `Failed to open ${message}.`,
body: error.stack ?? error.message,
})
}
}

export function renderLinks(store: string, themeId: string, host = DEFAULT_HOST, port = DEFAULT_PORT) {
const remoteUrl = `https://${store}`
const localUrl = `http://${host}:${port}`
export function renderLinks(urls: {local: string; giftCard: string; themeEditor: string; preview: string}) {
renderSuccess({
body: [
{
list: {
title: {bold: 'Preview your theme'},
items: [localUrl],
title: chalk.bold('Preview your theme ') + chalk.cyan('(t)'),
items: [
{
link: {
url: urls.local,
},
},
],
},
},
],
nextSteps: [
[
{
link: {
label: 'Preview your gift cards',
url: `${localUrl}/gift_cards/[store_id]/preview`,
label: `Share your theme preview ${chalk.cyan('(p)')}`,
url: urls.preview,
},
},
{
subdued: urls.preview,
},
],
[
{
link: {
label: 'Customize your theme at the theme editor',
url: `${remoteUrl}/admin/themes/${themeId}/editor`,
label: `Customize your theme at the theme editor ${chalk.cyan('(e)')}`,
url: urls.themeEditor,
},
},
],
[
'Share your theme preview',
{
subdued: `\n${remoteUrl}/?preview_theme_id=${themeId}`,
link: {
label: `Preview your gift cards ${chalk.cyan('(g)')}`,
url: urls.giftCard,
},
},
],
],
Expand Down

0 comments on commit 45d6424

Please sign in to comment.