Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/blue-taxes-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': minor
---

Added a separate Dev Console link to the `app dev` output for non-embedded apps
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface DevSessionStatus {
isReady: boolean
previewURL?: string
graphiqlURL?: string
appEmbedded?: boolean
hasExtensions?: boolean
statusMessage?: {message: string; type: DevSessionStatusMessageType}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,12 @@ export class DevSession {
const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0
const useDevConsole = firstPartyDev() && hasPreview
const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL
this.statusManager.updateStatus({previewURL: newPreviewURL})
const hasExtensions = event.app.nonConfigExtensions.length > 0
this.statusManager.updateStatus({
previewURL: newPreviewURL,
appEmbedded: event.app.configuration.embedded,
hasExtensions,
})
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,15 @@ export async function setupDevProcesses({
? `http://localhost:${graphiqlPort}/graphiql?key=${encodeURIComponent(resolvedGraphiqlKey)}`
: undefined

const devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL})
const appEmbedded = reloadedApp.configuration.embedded
const hasExtensions = reloadedApp.nonConfigExtensions.length > 0
const devSessionStatusManager = new DevSessionStatusManager({
isReady: false,
previewURL,
graphiqlURL,
appEmbedded,
hasExtensions,
})

const processes = [
...(await setupWebProcesses({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const initialStatus: DevSessionStatus = {
isReady: true,
previewURL: 'https://shopify.com',
graphiqlURL: 'https://graphiql.shopify.com',
appEmbedded: false,
hasExtensions: true,
}

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

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

renderInstance.unmount()
})
Expand Down Expand Up @@ -171,6 +175,80 @@ describe('DevSessionUI', () => {
renderInstance.unmount()
})

test('opens the dev console URL when c is pressed for non-embedded apps', async () => {
// Given
devSessionStatusManager.updateStatus({appEmbedded: false})

// When
const renderInstance = render(
<DevSessionUI
processes={[]}
abortController={new AbortController()}
devSessionStatusManager={devSessionStatusManager}
shopFqdn="mystore.myshopify.com"
onAbort={onAbort}
/>,
)

await waitForInputsToBeReady()
await sendInputAndWait(renderInstance, 10, 'c')

// Then
expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://mystore.myshopify.com/admin?dev-console=show')

renderInstance.unmount()
})

test('does not show dev console shortcut when app is embedded', async () => {
// Given
devSessionStatusManager.updateStatus({appEmbedded: true})

// When
const renderInstance = render(
<DevSessionUI
processes={[]}
abortController={new AbortController()}
devSessionStatusManager={devSessionStatusManager}
shopFqdn="mystore.myshopify.com"
onAbort={onAbort}
/>,
)

await waitForInputsToBeReady()

// Then
const output = unstyled(renderInstance.lastFrame()!)
expect(output).not.toContain('(c) Open Dev Console')
expect(output).not.toContain('Dev Console URL')

renderInstance.unmount()
})

test('does not show dev console shortcut when app has no extensions', async () => {
// Given
devSessionStatusManager.updateStatus({hasExtensions: false})

// When
const renderInstance = render(
<DevSessionUI
processes={[]}
abortController={new AbortController()}
devSessionStatusManager={devSessionStatusManager}
shopFqdn="mystore.myshopify.com"
onAbort={onAbort}
/>,
)

await waitForInputsToBeReady()

// Then
const output = unstyled(renderInstance.lastFrame()!)
expect(output).not.toContain('(c) Open Dev Console')
expect(output).not.toContain('Dev Console URL')

renderInstance.unmount()
})

test('quits when q is pressed', async () => {
// Given
const abortController = new AbortController()
Expand Down Expand Up @@ -356,7 +434,7 @@ describe('DevSessionUI', () => {
await waitForInputsToBeReady()

// Initial state
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('preview in your browser')
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Open app preview')

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

await waitForContent(renderInstance, 'Preview in your browser')
await waitForContent(renderInstance, 'Open app preview')

// Then
expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://new-preview-url.shopify.com')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DevSessionStatusMessageType,
} from '../../processes/dev-session/dev-session-status-manager.js'
import {MAX_EXTENSION_HANDLE_LENGTH} from '../../../../models/extensions/schemas.js'
import {buildDevConsoleURL} from '../../../../utilities/app/app-url.js'
import {OutputProcess} from '@shopify/cli-kit/node/output'
import {Alert, ConcurrentOutput, Link, TabularData} from '@shopify/cli-kit/node/ui/components'
import {useAbortSignal} from '@shopify/cli-kit/node/ui/hooks'
Expand Down Expand Up @@ -147,6 +148,16 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
}
},
},
{
key: 'c',
condition: () => Boolean(status.isReady && !status.appEmbedded && status.hasExtensions),
action: async () => {
await metadata.addPublicMetadata(() => ({
cmd_dev_preview_url_opened: true,
}))
await openURL(buildDevConsoleURL(shopFqdn))
},
},
],
content: (
<>
Expand All @@ -157,14 +168,19 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
)}
{canUseShortcuts && (
<Box marginTop={1} flexDirection="column">
{status.graphiqlURL && status.isReady ? (
{status.isReady ? (
<Text>
{figures.pointerSmall} <Text bold>(g)</Text> Open GraphiQL (Admin API) in your browser
{figures.pointerSmall} <Text bold>(p)</Text> Open app preview
</Text>
) : null}
{status.isReady ? (
{status.isReady && !status.appEmbedded && status.hasExtensions ? (
<Text>
{figures.pointerSmall} <Text bold>(c)</Text> Open Dev Console for extension previews
</Text>
) : null}
{status.graphiqlURL && status.isReady ? (
<Text>
{figures.pointerSmall} <Text bold>(p)</Text> Preview in your browser
{figures.pointerSmall} <Text bold>(g)</Text> Open GraphiQL (Admin API)
</Text>
) : null}
</Box>
Expand All @@ -181,6 +197,11 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
Preview URL: <Link url={status.previewURL} />
</Text>
) : null}
{status.appEmbedded === false && status.hasExtensions ? (
<Text>
Dev Console URL: <Link url={buildDevConsoleURL(shopFqdn)} />
</Text>
) : null}
{status.graphiqlURL ? (
<Text>
GraphiQL URL: <Link url={status.graphiqlURL} />
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/cli/utilities/app/app-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export function buildAppURLForAdmin(storeFqdn: string, apiKey: string, adminDoma
return `https://${adminDomain}/store/${storeName}/apps/${apiKey}?dev-console=show`
}

export function buildDevConsoleURL(storeFqdn: string) {
return `https://${storeFqdn}/admin?dev-console=show`
}

export function buildAppURLForMobile(storeFqdn: string, apiKey: string) {
const normalizedFQDN = normalizeStoreFqdn(storeFqdn)
const adminUrl = storeAdminUrl(normalizedFQDN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ export class AppManagementClient implements DeveloperPlatformClient {
organizationId: String(numberFromGid(app.organizationId)),
grantedScopes: app.activeRoot.grantedShopifyApprovalScopes,
applicationUrl: appHomeModule?.config?.app_url as string | undefined,
embedded: appHomeModule?.config?.embedded as boolean | undefined,
flags: [],
developerPlatformClient: this,
}
Expand Down
Loading