Skip to content

Commit 9301fe3

Browse files
committed
Remove duplicated dev-console url builder
1 parent 33f6fb8 commit 9301fe3

File tree

8 files changed

+197
-22
lines changed

8 files changed

+197
-22
lines changed

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

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import {setupDevSessionProcess, pushUpdatesForDevSession, resetDevSessionStatus} from './dev-session.js'
1+
import {
2+
setupDevSessionProcess,
3+
pushUpdatesForDevSession,
4+
resetDevSessionStatus,
5+
devSessionStatus,
6+
} from './dev-session.js'
27
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
38
import {AppLinkedInterface} from '../../../models/app/app.js'
49
import {AppEventWatcher} from '../app-events/app-event-watcher.js'
@@ -7,6 +12,7 @@ import {
712
testAppAccessConfigExtension,
813
testAppLinked,
914
testDeveloperPlatformClient,
15+
testFlowActionExtension,
1016
testUIExtension,
1117
testWebhookExtensions,
1218
} from '../../../models/app/app.test-data.js'
@@ -35,6 +41,8 @@ describe('setupDevSessionProcess', () => {
3541
organizationId: 'org123',
3642
appId: 'app123',
3743
appWatcher: {} as AppEventWatcher,
44+
appPreviewURL: 'https://test.preview.url',
45+
appLocalProxyURL: 'https://test.local.url',
3846
}
3947

4048
// When
@@ -54,6 +62,8 @@ describe('setupDevSessionProcess', () => {
5462
organizationId: options.organizationId,
5563
appId: options.appId,
5664
appWatcher: options.appWatcher,
65+
appPreviewURL: options.appPreviewURL,
66+
appLocalProxyURL: options.appLocalProxyURL,
5767
},
5868
})
5969
})
@@ -84,6 +94,8 @@ describe('pushUpdatesForDevSession', () => {
8494
storeFqdn: 'test.myshopify.com',
8595
appId: 'app123',
8696
organizationId: 'org123',
97+
appPreviewURL: 'https://test.preview.url',
98+
appLocalProxyURL: 'https://test.local.url',
8799
}
88100
})
89101

@@ -205,4 +217,60 @@ describe('pushUpdatesForDevSession', () => {
205217
expect(developerPlatformClient.refreshToken).toHaveBeenCalledOnce()
206218
expect(developerPlatformClient.devSessionUpdate).toHaveBeenCalledTimes(2)
207219
})
220+
221+
test('updates preview URL when extension is previewable', async () => {
222+
// Given
223+
const extension = await testUIExtension({type: 'ui_extension'})
224+
const newApp = testAppLinked({allExtensions: [extension]})
225+
226+
// When
227+
await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options)
228+
await appWatcher.start({stdout, stderr, signal: abortController.signal})
229+
await flushPromises()
230+
appWatcher.emit('all', {app: newApp, extensionEvents: [{type: 'updated', extension}]})
231+
await flushPromises()
232+
233+
// Then
234+
expect(devSessionStatus().devSessionPreviewURL).toBe(options.appLocalProxyURL)
235+
})
236+
237+
test('updates preview URL to appPreviewURL when no previewable extensions', async () => {
238+
// Given
239+
const extension = await testFlowActionExtension()
240+
const newApp = testAppLinked({allExtensions: [extension]})
241+
242+
// When
243+
await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options)
244+
await appWatcher.start({stdout, stderr, signal: abortController.signal})
245+
await flushPromises()
246+
appWatcher.emit('all', {app: newApp, extensionEvents: [{type: 'updated', extension}]})
247+
await flushPromises()
248+
249+
// Then
250+
expect(devSessionStatus().devSessionPreviewURL).toBe(options.appPreviewURL)
251+
})
252+
253+
test('resets dev session status when calling resetDevSessionStatus', async () => {
254+
// Given
255+
const extension = await testUIExtension({type: 'ui_extension'})
256+
const newApp = testAppLinked({allExtensions: [extension]})
257+
258+
// When
259+
await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options)
260+
await appWatcher.start({stdout, stderr, signal: abortController.signal})
261+
await flushPromises()
262+
appWatcher.emit('all', {app: newApp, extensionEvents: [{type: 'updated', extension}]})
263+
await flushPromises()
264+
265+
// Then
266+
expect(devSessionStatus().isDevSessionReady).toBe(true)
267+
expect(devSessionStatus().devSessionPreviewURL).toBeDefined()
268+
269+
// When
270+
resetDevSessionStatus()
271+
272+
// Then
273+
expect(devSessionStatus().isDevSessionReady).toBe(false)
274+
expect(devSessionStatus().devSessionPreviewURL).toBeUndefined()
275+
})
208276
})

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface DevSessionOptions {
2828
organizationId: string
2929
appId: string
3030
appWatcher: AppEventWatcher
31+
appPreviewURL: string
32+
appLocalProxyURL: string
3133
}
3234

3335
interface DevSessionProcessOptions extends DevSessionOptions {
@@ -65,15 +67,18 @@ let bundleControllers: AbortController[] = []
6567
// Current status of the dev session
6668
// Since the watcher can emit events before the dev session is ready, we need to keep track of the status
6769
let isDevSessionReady = false
70+
let devSessionPreviewURL: string | undefined
6871

6972
export function devSessionStatus() {
7073
return {
7174
isDevSessionReady,
75+
devSessionPreviewURL,
7276
}
7377
}
7478

7579
export function resetDevSessionStatus() {
7680
isDevSessionReady = false
81+
devSessionPreviewURL = undefined
7782
}
7883

7984
export async function setupDevSessionProcess({
@@ -116,6 +121,8 @@ export const pushUpdatesForDevSession: DevProcessFunction<DevSessionOptions> = a
116121
const anyError = event.extensionEvents.some((eve) => eve.buildResult?.status === 'error')
117122
if (anyError) return
118123

124+
await updatePreviewURL(processOptions, event)
125+
119126
// Cancel any ongoing bundle and upload process
120127
bundleControllers.forEach((controller) => controller.abort())
121128
// Remove aborted controllers from array:
@@ -363,3 +370,8 @@ async function printLogMessage(message: string, stdout: Writable, prefix?: strin
363370
stdout.write(message)
364371
})
365372
}
373+
374+
async function updatePreviewURL(options: DevSessionProcessOptions, event: AppEvent) {
375+
const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0
376+
devSessionPreviewURL = hasPreview ? options.appLocalProxyURL : options.appPreviewURL
377+
}

packages/app/src/cli/services/dev/processes/previewable-extension.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ export async function setupPreviewableExtensionsProcess({
8181
checkoutCartUrl?: string
8282
}): Promise<PreviewableExtensionProcess | undefined> {
8383
const previewableExtensions = allExtensions.filter((ext) => ext.isPreviewable)
84-
if (previewableExtensions.length === 0) {
85-
return
86-
}
8784

8885
const cartUrl = await buildCartURLIfNeeded(previewableExtensions, storeFqdn, checkoutCartUrl)
8986

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ describe('setup-dev-processes', () => {
337337
graphiqlKey: 'somekey',
338338
})
339339

340-
expect(res.processes[2]).toMatchObject({
340+
expect(res.processes[3]).toMatchObject({
341341
type: 'dev-session',
342342
prefix: 'dev-session',
343343
function: pushUpdatesForDevSession,

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ export async function setupDevProcesses({
9090
const reloadedApp = await reloadApp(localApp)
9191
const appWatcher = new AppEventWatcher(reloadedApp, network.proxyUrl)
9292

93+
// Decide on the appropriate preview URL for a session with these processes
94+
const anyPreviewableExtensions = reloadedApp.allExtensions.some((ext) => ext.isPreviewable)
95+
const devConsoleURL = `${network.proxyUrl}/extensions/dev-console`
96+
const previewUrl = anyPreviewableExtensions ? devConsoleURL : appPreviewUrl
97+
9398
const processes = [
9499
...(await setupWebProcesses({
95100
webs: reloadedApp.webs,
@@ -136,6 +141,8 @@ export async function setupDevProcesses({
136141
organizationId: remoteApp.organizationId,
137142
storeFqdn,
138143
appWatcher,
144+
appPreviewURL: appPreviewUrl,
145+
appLocalProxyURL: devConsoleURL,
139146
})
140147
: await setupDraftableExtensionsProcess({
141148
localApp: reloadedApp,
@@ -180,10 +187,6 @@ export async function setupDevProcesses({
180187
// Add http server proxy & configure ports, for processes that need it
181188
const processesWithProxy = await setPortsAndAddProxyProcess(processes, network.proxyPort)
182189

183-
// Decide on the appropriate preview URL for a session with these processes
184-
const anyPreviewableExtensions = processesWithProxy.filter((process) => process.type === 'previewable-extension')
185-
const previewUrl = anyPreviewableExtensions.length > 0 ? `${network.proxyUrl}/extensions/dev-console` : appPreviewUrl
186-
187190
return {
188191
processes: processesWithProxy,
189192
previewUrl,

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

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const developerPreview = {
4343

4444
describe('Dev', () => {
4545
beforeEach(() => {
46-
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true})
46+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: undefined})
4747
})
4848

4949
test('renders a stream of concurrent outputs from sub-processes, shortcuts and a preview url', async () => {
@@ -977,7 +977,7 @@ describe('Dev', () => {
977977

978978
test('updates UI when devSessionEnabled changes from false to true', async () => {
979979
// Given
980-
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: false})
980+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: false, devSessionPreviewURL: undefined})
981981

982982
const renderInstance = render(
983983
<Dev
@@ -1012,7 +1012,7 @@ describe('Dev', () => {
10121012
`)
10131013

10141014
// When dev session becomes ready
1015-
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true})
1015+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: 'https://shopify.com'})
10161016

10171017
// Wait for the polling interval to update the UI
10181018
await waitForContent(renderInstance, 'preview in your browser')
@@ -1034,6 +1034,90 @@ describe('Dev', () => {
10341034
// unmount so that polling is cleared after every test
10351035
renderInstance.unmount()
10361036
})
1037+
1038+
test('updates preview URL when devSessionStatus provides a new URL', async () => {
1039+
// Given
1040+
const initialPreviewUrl = 'https://shopify.com'
1041+
const newPreviewUrl = 'https://my-new-preview-url.shopify.com'
1042+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: undefined})
1043+
1044+
const renderInstance = render(
1045+
<Dev
1046+
processes={[]}
1047+
abortController={new AbortController()}
1048+
previewUrl={initialPreviewUrl}
1049+
graphiqlUrl="https://graphiql.shopify.com"
1050+
graphiqlPort={1234}
1051+
app={{
1052+
...testApp,
1053+
developerPlatformClient: {
1054+
...testDeveloperPlatformClient(),
1055+
supportsDevSessions: true,
1056+
},
1057+
}}
1058+
developerPreview={developerPreview}
1059+
shopFqdn="mystore.shopify.io"
1060+
/>,
1061+
)
1062+
1063+
// Initial state should show the default preview URL
1064+
expect(unstyled(renderInstance.lastFrame()!)).toContain(`Preview URL: ${initialPreviewUrl}`)
1065+
1066+
// When dev session provides a new preview URL
1067+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: newPreviewUrl})
1068+
1069+
// Wait for the polling interval to update the UI
1070+
await waitForContent(renderInstance, newPreviewUrl)
1071+
1072+
// Then - status message should show the new preview URL
1073+
expect(unstyled(renderInstance.lastFrame()!)).toContain(`Preview URL: ${newPreviewUrl}`)
1074+
1075+
// unmount so that polling is cleared after every test
1076+
renderInstance.unmount()
1077+
})
1078+
1079+
test('opens the updated preview URL when p is pressed after URL changes', async () => {
1080+
// Given
1081+
const initialPreviewUrl = 'https://shopify.com'
1082+
const newPreviewUrl = 'https://my-new-preview-url.shopify.com'
1083+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: undefined})
1084+
1085+
const renderInstance = render(
1086+
<Dev
1087+
processes={[]}
1088+
abortController={new AbortController()}
1089+
previewUrl={initialPreviewUrl}
1090+
graphiqlUrl="https://graphiql.shopify.com"
1091+
graphiqlPort={1234}
1092+
app={{
1093+
...testApp,
1094+
developerPlatformClient: {
1095+
...testDeveloperPlatformClient(),
1096+
supportsDevSessions: true,
1097+
},
1098+
}}
1099+
developerPreview={developerPreview}
1100+
shopFqdn="mystore.shopify.io"
1101+
/>,
1102+
)
1103+
1104+
// Initial state should show the default preview URL
1105+
expect(unstyled(renderInstance.lastFrame()!)).toContain(`Preview URL: ${initialPreviewUrl}`)
1106+
1107+
// When dev session provides a new preview URL
1108+
vi.mocked(devSessionStatus).mockReturnValue({isDevSessionReady: true, devSessionPreviewURL: newPreviewUrl})
1109+
1110+
// Wait for the polling interval to update the UI
1111+
await waitForContent(renderInstance, newPreviewUrl)
1112+
1113+
await waitForInputsToBeReady()
1114+
await sendInputAndWait(renderInstance, 100, 'p')
1115+
1116+
// Then
1117+
expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, newPreviewUrl)
1118+
1119+
renderInstance.unmount()
1120+
})
10371121
})
10381122

10391123
describe('calculatePrefixColumnSize', () => {

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,22 @@ const Dev: FunctionComponent<DevProps> = ({
6767
const pollingInterval = useRef<NodeJS.Timeout>()
6868
const devSessionPollingInterval = useRef<NodeJS.Timeout>()
6969
const localhostGraphiqlUrl = `http://localhost:${graphiqlPort}/graphiql`
70-
const defaultStatusMessage = `Preview URL: ${previewUrl}${
70+
const defaultPreviewURL = previewUrl
71+
const defaultStatusMessage = `Preview URL: ${defaultPreviewURL}${
7172
graphiqlUrl ? `\nGraphiQL URL: ${localhostGraphiqlUrl}` : ''
7273
}`
7374
const [statusMessage, setStatusMessage] = useState(defaultStatusMessage)
75+
const [devPreviewEnabled, setDevPreviewEnabled] = useState<boolean>(true)
76+
const [devSessionEnabled, setDevSessionEnabled] = useState<boolean>(devSessionStatus().isDevSessionReady)
77+
const [appPreviewURL, setAppPreviewURL] = useState<string>(defaultPreviewURL)
78+
const [error, setError] = useState<string | undefined>(undefined)
79+
80+
// This is a flag to prevent the status message from being updated when the dev is shutting down
81+
// Will be updated in a future PR to use states.
82+
let isShuttingDown = false
7483

7584
const {isAborted} = useAbortSignal(abortController.signal, async (err) => {
85+
isShuttingDown = true
7686
if (err) {
7787
setStatusMessage('Shutting down dev because of an error ...')
7888
} else {
@@ -90,10 +100,6 @@ const Dev: FunctionComponent<DevProps> = ({
90100
await developerPreview.disable()
91101
})
92102

93-
const [devPreviewEnabled, setDevPreviewEnabled] = useState<boolean>(true)
94-
const [devSessionEnabled, setDevSessionEnabled] = useState<boolean>(devSessionStatus().isDevSessionReady)
95-
const [error, setError] = useState<string | undefined>(undefined)
96-
97103
const errorHandledProcesses = useMemo(() => {
98104
return processes.map((process) => {
99105
return {
@@ -121,9 +127,9 @@ const Dev: FunctionComponent<DevProps> = ({
121127
*/
122128
useEffect(() => {
123129
const pollDevSession = async () => {
124-
const {isDevSessionReady} = devSessionStatus()
130+
const {isDevSessionReady, devSessionPreviewURL} = devSessionStatus()
125131
setDevSessionEnabled(isDevSessionReady)
126-
if (isDevSessionReady) clearInterval(devSessionPollingInterval.current)
132+
if (devSessionPreviewURL && appPreviewURL !== devSessionPreviewURL) setAppPreviewURL(devSessionPreviewURL)
127133
}
128134

129135
if (app.developerPlatformClient.supportsDevSessions) {
@@ -136,6 +142,11 @@ const Dev: FunctionComponent<DevProps> = ({
136142
return () => clearInterval(devSessionPollingInterval.current)
137143
}, [devSessionStatus])
138144

145+
useEffect(() => {
146+
const newMessage = `Preview URL: ${appPreviewURL}${graphiqlUrl ? `\nGraphiQL URL: ${localhostGraphiqlUrl}` : ''}`
147+
if (!isShuttingDown) setStatusMessage(newMessage)
148+
}, [appPreviewURL])
149+
139150
useEffect(() => {
140151
const pollDevPreviewMode = async () => {
141152
try {
@@ -190,11 +201,11 @@ const Dev: FunctionComponent<DevProps> = ({
190201
try {
191202
setError('')
192203

193-
if (input === 'p' && previewUrl && devSessionEnabled) {
204+
if (input === 'p' && appPreviewURL && devSessionEnabled) {
194205
await metadata.addPublicMetadata(() => ({
195206
cmd_dev_preview_url_opened: true,
196207
}))
197-
await openURL(previewUrl)
208+
await openURL(appPreviewURL)
198209
} else if (input === 'g' && graphiqlUrl && devSessionEnabled) {
199210
await metadata.addPublicMetadata(() => ({
200211
cmd_dev_graphiql_opened: true,

0 commit comments

Comments
 (0)