Skip to content

Commit

Permalink
Merge pull request #4830 from Shopify/add-shortcut-keys-to-theme-dev
Browse files Browse the repository at this point in the history
[Feature] Add shortcut keys to theme dev
  • Loading branch information
EvilGenius13 authored Nov 22, 2024
2 parents d6b1598 + 057e2c6 commit 7df6667
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 7df6667

Please sign in to comment.