diff --git a/.changeset/blue-taxes-cry.md b/.changeset/blue-taxes-cry.md
new file mode 100644
index 00000000000..1da22306571
--- /dev/null
+++ b/.changeset/blue-taxes-cry.md
@@ -0,0 +1,5 @@
+---
+'@shopify/app': minor
+---
+
+Added a separate Dev Console link to the `app dev` output for non-embedded apps
diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts
index 027caff0bcf..8b2669c2404 100644
--- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts
+++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session-status-manager.ts
@@ -17,6 +17,7 @@ export interface DevSessionStatus {
isReady: boolean
previewURL?: string
graphiqlURL?: string
+ appEmbedded?: boolean
statusMessage?: {message: string; type: DevSessionStatusMessageType}
}
diff --git a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts
index 1e9602ce520..3a94d858f46 100644
--- a/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts
+++ b/packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts
@@ -265,7 +265,7 @@ 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})
+ this.statusManager.updateStatus({previewURL: newPreviewURL, appEmbedded: event.app.configuration.embedded})
}
/**
diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts
index 9a2a3723740..3d6d9f4dd20 100644
--- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts
+++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.ts
@@ -125,7 +125,8 @@ 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 devSessionStatusManager = new DevSessionStatusManager({isReady: false, previewURL, graphiqlURL, appEmbedded})
const processes = [
...(await setupWebProcesses({
diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx
index ac93a0479fc..896547607ea 100644
--- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx
+++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.test.tsx
@@ -40,6 +40,7 @@ const initialStatus: DevSessionStatus = {
isReady: true,
previewURL: 'https://shopify.com',
graphiqlURL: 'https://graphiql.shopify.com',
+ appEmbedded: false,
}
const onAbort = vi.fn()
@@ -121,10 +122,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()
})
@@ -171,6 +174,55 @@ 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(
+ ,
+ )
+
+ 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(
+ ,
+ )
+
+ 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()
@@ -356,7 +408,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({
@@ -365,7 +417,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')
diff --git a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx
index 35f3cf44187..af31a0d79fc 100644
--- a/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx
+++ b/packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx
@@ -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'
@@ -147,6 +148,16 @@ const DevSessionUI: FunctionComponent = ({
}
},
},
+ {
+ key: 'c',
+ condition: () => Boolean(!status.appEmbedded && status.isReady),
+ action: async () => {
+ await metadata.addPublicMetadata(() => ({
+ cmd_dev_preview_url_opened: true,
+ }))
+ await openURL(buildDevConsoleURL(shopFqdn))
+ },
+ },
],
content: (
<>
@@ -157,14 +168,19 @@ const DevSessionUI: FunctionComponent = ({
)}
{canUseShortcuts && (
- {status.graphiqlURL && status.isReady ? (
+ {status.isReady ? (
- {figures.pointerSmall} (g) Open GraphiQL (Admin API) in your browser
+ {figures.pointerSmall} (p) Open app preview
) : null}
- {status.isReady ? (
+ {!status.appEmbedded && status.isReady ? (
+
+ {figures.pointerSmall} (c) Open Dev Console for extension previews
+
+ ) : null}
+ {status.graphiqlURL && status.isReady ? (
- {figures.pointerSmall} (p) Preview in your browser
+ {figures.pointerSmall} (g) Open GraphiQL (Admin API)
) : null}
@@ -181,6 +197,11 @@ const DevSessionUI: FunctionComponent = ({
Preview URL:
) : null}
+ {status.appEmbedded === false ? (
+
+ Dev Console URL:
+
+ ) : null}
{status.graphiqlURL ? (
GraphiQL URL:
diff --git a/packages/app/src/cli/utilities/app/app-url.ts b/packages/app/src/cli/utilities/app/app-url.ts
index 79afa5cd860..f201f1be874 100644
--- a/packages/app/src/cli/utilities/app/app-url.ts
+++ b/packages/app/src/cli/utilities/app/app-url.ts
@@ -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)
diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts
index 918d5775ccc..e9f8a6c98f3 100644
--- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts
+++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts
@@ -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,
}