Skip to content

Commit d8171c6

Browse files
Merge pull request #7263 from Shopify/nwesselman/dev-console-shortcut
Add Dev Console shortcut for non-embedded apps in app dev
2 parents 882b7f0 + 01d5396 commit d8171c6

8 files changed

Lines changed: 135 additions & 10 deletions

File tree

.changeset/blue-taxes-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/app': minor
3+
---
4+
5+
Added a separate Dev Console link to the `app dev` output for non-embedded apps

packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface DevSessionStatus {
1717
isReady: boolean
1818
previewURL?: string
1919
graphiqlURL?: string
20+
appEmbedded?: boolean
21+
hasExtensions?: boolean
2022
statusMessage?: {message: string; type: DevSessionStatusMessageType}
2123
}
2224

packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,12 @@ export class DevSession {
269269
const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0
270270
const useDevConsole = firstPartyDev() && hasPreview
271271
const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL
272-
this.statusManager.updateStatus({previewURL: newPreviewURL})
272+
const hasExtensions = event.app.nonConfigExtensions.length > 0
273+
this.statusManager.updateStatus({
274+
previewURL: newPreviewURL,
275+
appEmbedded: event.app.configuration.embedded,
276+
hasExtensions,
277+
})
273278
}
274279

275280
/**

packages/app/src/cli/services/dev/processes/setup-dev-processes.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,15 @@ export async function setupDevProcesses({
125125
? `http://localhost:${graphiqlPort}/graphiql?key=${encodeURIComponent(resolvedGraphiqlKey)}`
126126
: undefined
127127

128-
const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL})
128+
const appEmbedded = reloadedApp.configuration.embedded
129+
const hasExtensions = reloadedApp.nonConfigExtensions.length > 0
130+
const devSessionStatusManager = new DevSessionStatusManager({
131+
isReady: false,
132+
previewURL,
133+
graphiqlURL,
134+
appEmbedded,
135+
hasExtensions,
136+
})
129137

130138
const processes = [
131139
...(await setupWebProcesses({

packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const initialStatus: DevSessionStatus = {
4040
isReady: true,
4141
previewURL: 'https://shopify.com',
4242
graphiqlURL: 'https://graphiql.shopify.com',
43+
appEmbedded: false,
44+
hasExtensions: true,
4345
}
4446

4547
const onAbort = vi.fn()
@@ -121,10 +123,12 @@ describe('DevSessionUI', () => {
121123
expect(output).toContain('(q) Quit')
122124

123125
// Shortcuts and URLs should be visible
124-
expect(output).toContain('(g) Open GraphiQL')
125-
expect(output).toContain('(p) Preview in your browser')
126+
expect(output).toContain('(g) Open GraphiQL (Admin API)')
127+
expect(output).toContain('(p) Open app preview')
128+
expect(output).toContain('(c) Open Dev Console for extension previews')
126129
expect(output).toContain('Preview URL: https://shopify.com')
127130
expect(output).toContain('GraphiQL URL: https://graphiql.shopify.com')
131+
expect(output).toContain('Dev Console URL: https://mystore.myshopify.com/admin?dev-console=show')
128132

129133
renderInstance.unmount()
130134
})
@@ -171,6 +175,80 @@ describe('DevSessionUI', () => {
171175
renderInstance.unmount()
172176
})
173177

178+
test('opens the dev console URL when c is pressed for non-embedded apps', async () => {
179+
// Given
180+
devSessionStatusManager.updateStatus({appEmbedded: false})
181+
182+
// When
183+
const renderInstance = render(
184+
<DevSessionUI
185+
processes={[]}
186+
abortController={new AbortController()}
187+
devSessionStatusManager={devSessionStatusManager}
188+
shopFqdn="mystore.myshopify.com"
189+
onAbort={onAbort}
190+
/>,
191+
)
192+
193+
await waitForInputsToBeReady()
194+
await sendInputAndWait(renderInstance, 10, 'c')
195+
196+
// Then
197+
expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://mystore.myshopify.com/admin?dev-console=show')
198+
199+
renderInstance.unmount()
200+
})
201+
202+
test('does not show dev console shortcut when app is embedded', async () => {
203+
// Given
204+
devSessionStatusManager.updateStatus({appEmbedded: true})
205+
206+
// When
207+
const renderInstance = render(
208+
<DevSessionUI
209+
processes={[]}
210+
abortController={new AbortController()}
211+
devSessionStatusManager={devSessionStatusManager}
212+
shopFqdn="mystore.myshopify.com"
213+
onAbort={onAbort}
214+
/>,
215+
)
216+
217+
await waitForInputsToBeReady()
218+
219+
// Then
220+
const output = unstyled(renderInstance.lastFrame()!)
221+
expect(output).not.toContain('(c) Open Dev Console')
222+
expect(output).not.toContain('Dev Console URL')
223+
224+
renderInstance.unmount()
225+
})
226+
227+
test('does not show dev console shortcut when app has no extensions', async () => {
228+
// Given
229+
devSessionStatusManager.updateStatus({hasExtensions: false})
230+
231+
// When
232+
const renderInstance = render(
233+
<DevSessionUI
234+
processes={[]}
235+
abortController={new AbortController()}
236+
devSessionStatusManager={devSessionStatusManager}
237+
shopFqdn="mystore.myshopify.com"
238+
onAbort={onAbort}
239+
/>,
240+
)
241+
242+
await waitForInputsToBeReady()
243+
244+
// Then
245+
const output = unstyled(renderInstance.lastFrame()!)
246+
expect(output).not.toContain('(c) Open Dev Console')
247+
expect(output).not.toContain('Dev Console URL')
248+
249+
renderInstance.unmount()
250+
})
251+
174252
test('quits when q is pressed', async () => {
175253
// Given
176254
const abortController = new AbortController()
@@ -356,7 +434,7 @@ describe('DevSessionUI', () => {
356434
await waitForInputsToBeReady()
357435

358436
// Initial state
359-
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('preview in your browser')
437+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Open app preview')
360438

361439
// When status updates
362440
devSessionStatusManager.updateStatus({
@@ -365,7 +443,7 @@ describe('DevSessionUI', () => {
365443
graphiqlURL: 'https://new-graphiql.shopify.com',
366444
})
367445

368-
await waitForContent(renderInstance, 'Preview in your browser')
446+
await waitForContent(renderInstance, 'Open app preview')
369447

370448
// Then
371449
expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://new-preview-url.shopify.com')

packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DevSessionStatusMessageType,
88
} from '../../processes/dev-session/dev-session-status-manager.js'
99
import {MAX_EXTENSION_HANDLE_LENGTH} from '../../../../models/extensions/schemas.js'
10+
import {buildDevConsoleURL} from '../../../../utilities/app/app-url.js'
1011
import {OutputProcess} from '@shopify/cli-kit/node/output'
1112
import {Alert, ConcurrentOutput, Link, TabularData} from '@shopify/cli-kit/node/ui/components'
1213
import {useAbortSignal} from '@shopify/cli-kit/node/ui/hooks'
@@ -147,6 +148,16 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
147148
}
148149
},
149150
},
151+
{
152+
key: 'c',
153+
condition: () => Boolean(status.isReady && status.appEmbedded === false && status.hasExtensions),
154+
action: async () => {
155+
await metadata.addPublicMetadata(() => ({
156+
cmd_dev_preview_url_opened: true,
157+
}))
158+
await openURL(buildDevConsoleURL(shopFqdn))
159+
},
160+
},
150161
],
151162
content: (
152163
<>
@@ -157,14 +168,19 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
157168
)}
158169
{canUseShortcuts && (
159170
<Box marginTop={1} flexDirection="column">
160-
{status.graphiqlURL && status.isReady ? (
171+
{status.isReady ? (
161172
<Text>
162-
{figures.pointerSmall} <Text bold>(g)</Text> Open GraphiQL (Admin API) in your browser
173+
{figures.pointerSmall} <Text bold>(p)</Text> Open app preview
163174
</Text>
164175
) : null}
165-
{status.isReady ? (
176+
{status.isReady && !status.appEmbedded && status.hasExtensions ? (
177+
<Text>
178+
{figures.pointerSmall} <Text bold>(c)</Text> Open Dev Console for extension previews
179+
</Text>
180+
) : null}
181+
{status.graphiqlURL && status.isReady ? (
166182
<Text>
167-
{figures.pointerSmall} <Text bold>(p)</Text> Preview in your browser
183+
{figures.pointerSmall} <Text bold>(g)</Text> Open GraphiQL (Admin API)
168184
</Text>
169185
) : null}
170186
</Box>
@@ -181,6 +197,11 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
181197
Preview URL: <Link url={status.previewURL} />
182198
</Text>
183199
) : null}
200+
{status.appEmbedded === false && status.hasExtensions ? (
201+
<Text>
202+
Dev Console URL: <Link url={buildDevConsoleURL(shopFqdn)} />
203+
</Text>
204+
) : null}
184205
{status.graphiqlURL ? (
185206
<Text>
186207
GraphiQL URL: <Link url={status.graphiqlURL} />

packages/app/src/cli/utilities/app/app-url.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ export function buildAppURLForAdmin(storeFqdn: string, apiKey: string, adminDoma
1313
return `https://${adminDomain}/store/${storeName}/apps/${apiKey}?dev-console=show`
1414
}
1515

16+
export function buildDevConsoleURL(storeFqdn: string) {
17+
const normalizedFQDN = normalizeStoreFqdn(storeFqdn)
18+
return `https://${normalizedFQDN}/admin?dev-console=show`
19+
}
20+
1621
export function buildAppURLForMobile(storeFqdn: string, apiKey: string) {
1722
const normalizedFQDN = normalizeStoreFqdn(storeFqdn)
1823
const adminUrl = storeAdminUrl(normalizedFQDN)

packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ export class AppManagementClient implements DeveloperPlatformClient {
367367
organizationId: String(numberFromGid(app.organizationId)),
368368
grantedScopes: app.activeRoot.grantedShopifyApprovalScopes,
369369
applicationUrl: appHomeModule?.config?.app_url as string | undefined,
370+
embedded: appHomeModule?.config?.embedded as boolean | undefined,
370371
flags: [],
371372
developerPlatformClient: this,
372373
}

0 commit comments

Comments
 (0)