Skip to content

Commit edcd74c

Browse files
Respond to PR Feedback
1. --localhost-port no longer requires the --use-localhost flag 2. If --localhost-port is specified, but the port is not free, the process exist with a warning message 3. Improve the argument typing for generateFrontendURL()
1 parent 06eaa06 commit edcd74c

File tree

6 files changed

+127
-123
lines changed

6 files changed

+127
-123
lines changed

packages/app/src/cli/commands/app/dev.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,8 @@ If you're using the Ruby app template, then you need to complete the following s
9595
}),
9696
'localhost-port': Flags.integer({
9797
hidden: true,
98-
description: 'Port to use for localhost. Only applicable when --use-localhost is specified.',
98+
description: 'Port to use for localhost.',
9999
env: 'SHOPIFY_FLAG_LOCALHOST_PORT',
100-
dependsOn: ['use-localhost'],
101100
}),
102101
theme: Flags.string({
103102
hidden: false,

packages/app/src/cli/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const blocks = {
3737

3838
export const ports = {
3939
graphiql: 3457,
40-
localhost: 3568,
40+
localhost: 3458,
4141
} as const
4242

4343
export const EsbuildEnvVarRegex = /^([a-zA-Z_$])([a-zA-Z0-9_$])*$/

packages/app/src/cli/services/dev.test.ts

+60-64
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,49 @@ describe('warnIfScopesDifferBeforeDev', () => {
110110
})
111111
})
112112

113+
describe('getTunnelMode() if tunnelUrl is defined', () => {
114+
const defaultOptions = {
115+
useLocalhost: false,
116+
localhostPort: undefined,
117+
tunnelUrl: undefined,
118+
}
119+
120+
test('returns AutoTunnel', async () => {
121+
// Given
122+
const localhostPort = 1234
123+
vi.mocked(getAvailableTCPPort).mockResolvedValue(1234)
124+
125+
// When
126+
const result = (await getTunnelMode(defaultOptions)) as AutoTunnel
127+
128+
// Then
129+
expect(result).toMatchObject({mode: 'auto'})
130+
})
131+
})
132+
133+
describe('getTunnelMode() if useLocalhost is false and tunnelUrl is a string', () => {
134+
const defaultOptions = {
135+
useLocalhost: false,
136+
localhostPort: undefined,
137+
tunnelUrl: 'https://my-tunnel-url.com',
138+
}
139+
140+
test('returns CustomTunnel', async () => {
141+
// Given
142+
const localhostPort = 1234
143+
vi.mocked(getAvailableTCPPort).mockResolvedValue(1234)
144+
145+
// When
146+
const result = (await getTunnelMode(defaultOptions)) as CustomTunnel
147+
148+
// Then
149+
expect(result).toMatchObject({
150+
mode: 'custom',
151+
url: defaultOptions.tunnelUrl,
152+
})
153+
})
154+
})
155+
113156
describe('getTunnelMode() if useLocalhost is true', () => {
114157
const mockCertificate = {
115158
keyContent: 'test-key-content',
@@ -147,6 +190,20 @@ describe('getTunnelMode() if useLocalhost is true', () => {
147190
})
148191
})
149192

193+
test('throws when localhostPort is passed, but not available', async () => {
194+
// Given
195+
const localhostPort = 1234
196+
vi.mocked(getAvailableTCPPort).mockResolvedValue(4321)
197+
198+
// Then
199+
await expect(
200+
getTunnelMode({
201+
...defaultOptions,
202+
localhostPort,
203+
}),
204+
).rejects.toThrow()
205+
})
206+
150207
test('returns ports.localhost when localhostPort is not passed', async () => {
151208
// Given
152209
vi.mocked(getAvailableTCPPort).mockResolvedValue(ports.localhost)
@@ -178,29 +235,11 @@ describe('getTunnelMode() if useLocalhost is true', () => {
178235
expect(mockOutput.info()).toContain('Localhost-based development is in developer preview')
179236
})
180237

181-
test('Shows warning if requestedPort is not available', async () => {
182-
// Given
183-
const requestedPort = 3000
184-
const availablePort = 3001
185-
vi.mocked(checkPortAvailability).mockResolvedValue(false)
186-
vi.mocked(getAvailableTCPPort).mockResolvedValue(availablePort)
187-
188-
// When
189-
const mockOutput = mockAndCaptureOutput()
190-
mockOutput.clear()
191-
const result = (await getTunnelMode(defaultOptions)) as NoTunnel
192-
await result.provideCertificate('app-directory')
193-
194-
// Then
195-
expect(result.port).toBe(availablePort)
196-
expect(mockOutput.warn()).toContain('A random port will be used for localhost')
197-
})
198-
199-
test('Shows warning if requestedPort is not passed and ports.localhost is not available', async () => {
238+
test('Renders warning if ports.localhost is not available', async () => {
200239
// Given
201-
const availablePort = 3001
240+
const availablePort = ports.localhost + 1
202241
vi.mocked(checkPortAvailability).mockResolvedValue(false)
203-
vi.mocked(getAvailableTCPPort).mockResolvedValue(availablePort)
242+
vi.mocked(getAvailableTCPPort).mockResolvedValue(ports.localhost + 1)
204243

205244
// When
206245
const mockOutput = mockAndCaptureOutput()
@@ -232,46 +271,3 @@ describe('getTunnelMode() if useLocalhost is true', () => {
232271
})
233272
})
234273
})
235-
236-
describe('getTunnelMode() if useLocalhost is false and tunnelUrl is a string', () => {
237-
const defaultOptions = {
238-
useLocalhost: false,
239-
localhostPort: undefined,
240-
tunnelUrl: 'https://my-tunnel-url.com',
241-
}
242-
243-
test('returns CustomTunnel', async () => {
244-
// Given
245-
const localhostPort = 1234
246-
vi.mocked(getAvailableTCPPort).mockResolvedValue(1234)
247-
248-
// When
249-
const result = (await getTunnelMode(defaultOptions)) as CustomTunnel
250-
251-
// Then
252-
expect(result).toMatchObject({
253-
mode: 'custom',
254-
url: defaultOptions.tunnelUrl,
255-
})
256-
})
257-
})
258-
259-
describe('getTunnelMode() if useLocalhost is false and tunnelUrl is undefined', () => {
260-
const defaultOptions = {
261-
useLocalhost: false,
262-
localhostPort: undefined,
263-
tunnelUrl: undefined,
264-
}
265-
266-
test('returns AutoTunnel', async () => {
267-
// Given
268-
const localhostPort = 1234
269-
vi.mocked(getAvailableTCPPort).mockResolvedValue(1234)
270-
271-
// When
272-
const result = (await getTunnelMode(defaultOptions)) as AutoTunnel
273-
274-
// Then
275-
expect(result).toMatchObject({mode: 'auto'})
276-
})
277-
})

packages/app/src/cli/services/dev.ts

+55-46
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ async function validateCustomPorts(webConfigs: Web[], graphiqlPort: number) {
526526
const portAvailable = await checkPortAvailability(graphiqlPort)
527527
if (!portAvailable) {
528528
const errorMessage = `Port ${graphiqlPort} is not available for serving GraphiQL.`
529-
const tryMessage = ['Choose a different port by setting the', {command: '--graphiql-port'}, 'flag.']
529+
const tryMessage = ['Choose a different port for the', {command: '--graphiql-port'}, 'flag.']
530530
throw new AbortError(errorMessage, tryMessage)
531531
}
532532
})(),
@@ -538,69 +538,78 @@ function setPreviousAppId(directory: string, apiKey: string) {
538538
}
539539

540540
/**
541-
* Gets the tunnel config for doing app dev
541+
* Gets the tunnel or localhost config for doing app dev
542542
* @param options - Options required for the config
543543
* @returns A tunnel configuration object
544544
*/
545-
546545
export async function getTunnelMode({
547546
useLocalhost,
548547
localhostPort,
549548
tunnelUrl,
550549
}: {
550+
tunnelUrl?: string
551551
useLocalhost?: boolean
552552
localhostPort?: number
553-
tunnelUrl?: string
554553
}): Promise<TunnelMode> {
555-
if (useLocalhost) {
556-
const requestedPort = localhostPort ?? ports.localhost
557-
const actualPort = await getAvailableTCPPort(requestedPort)
554+
// Developer brought their own tunnel
555+
if (tunnelUrl) {
556+
return {mode: 'custom', url: tunnelUrl}
557+
}
558558

559+
// CLI should create a tunnel
560+
if (!useLocalhost && !localhostPort) {
559561
return {
560-
mode: 'use-localhost',
561-
port: actualPort,
562-
provideCertificate: async (appDirectory) => {
563-
renderInfo({
564-
headline: 'Localhost-based development is in developer preview.',
565-
body: [
566-
'`--use-localhost` is not compatible with Shopify features which directly invoke your app',
567-
'(such as Webhooks, App proxy, and Flow actions), or those which require testing your app from another',
568-
'device (such as POS). Please report any issues and provide feedback on the dev community:',
569-
],
570-
link: {
571-
label: 'Create a feedback post',
572-
url: 'https://community.shopify.dev/new-topic?category=shopify-cli-libraries&tags=app-dev-on-localhost',
573-
},
574-
})
575-
576-
if (requestedPort !== actualPort) {
577-
renderWarning({
578-
headline: [
579-
'A random port will be used for localhost because',
580-
{command: `${requestedPort}`},
581-
'is not available.',
582-
],
583-
body: [
584-
'If you want to use a specific port, choose a different one or free up the one you requested. Then re-run the command with the',
585-
{command: '--localhost-port PORT'},
586-
'flag.',
587-
],
588-
})
589-
}
590-
591-
return generateCertificate({
592-
appDirectory,
593-
onRequiresConfirmation: generateCertificatePrompt,
594-
})
595-
},
562+
mode: 'auto',
596563
}
597564
}
598565

599-
if (tunnelUrl) {
600-
return {mode: 'custom', url: tunnelUrl}
566+
const requestedPort = localhostPort ?? ports.localhost
567+
const actualPort = await getAvailableTCPPort(requestedPort)
568+
569+
// The user specified a port. It's not available. Abort!
570+
if (localhostPort && actualPort !== requestedPort) {
571+
const errorMessage = `Port ${localhostPort} is not available.`
572+
const tryMessage = ['Choose a different port for the', {command: '--localhost-port'}, 'flag.']
573+
throw new AbortError(errorMessage, tryMessage)
601574
}
602575

603576
return {
604-
mode: 'auto',
577+
mode: 'use-localhost',
578+
port: actualPort,
579+
provideCertificate: async (appDirectory) => {
580+
renderInfo({
581+
headline: 'Localhost-based development is in developer preview.',
582+
body: [
583+
'`--use-localhost` is not compatible with Shopify features which directly invoke your app',
584+
'(such as Webhooks, App proxy, and Flow actions), or those which require testing your app from another',
585+
'device (such as POS). Please report any issues and provide feedback on the dev community:',
586+
],
587+
link: {
588+
label: 'Create a feedback post',
589+
url: 'https://community.shopify.dev/new-topic?category=shopify-cli-libraries&tags=app-dev-on-localhost',
590+
},
591+
})
592+
593+
// The user didn't specify a port. The default isn't available. Warn
594+
if (requestedPort !== actualPort) {
595+
renderWarning({
596+
headline: [
597+
'A random port will be used for localhost because',
598+
{command: `${requestedPort}`},
599+
'is not available.',
600+
],
601+
body: [
602+
'If you want to use a specific port, choose a different one or free up the one you requested. Then re-run the command with the',
603+
{command: '--localhost-port PORT'},
604+
'flag.',
605+
],
606+
})
607+
}
608+
609+
return generateCertificate({
610+
appDirectory,
611+
onRequiresConfirmation: generateCertificatePrompt,
612+
})
613+
},
605614
}
606615
}

packages/app/src/cli/services/dev/urls.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ export type FrontendURLOptions = UseLocalhostFrontendUrlOptions | UseTunnelFront
3333
interface UseLocalhostFrontendUrlOptions {
3434
noTunnelUseLocalhost: true
3535
port: number
36-
tunnelUrl?: undefined
37-
tunnelClient?: undefined
3836
}
3937

4038
interface UseTunnelFrontendUrlOptions {
@@ -62,6 +60,14 @@ interface FrontendURLResult {
6260
* If there is no cached tunnel plugin and a tunnel is necessary, we'll ask the user to confirm.
6361
*/
6462
export async function generateFrontendURL(options: FrontendURLOptions): Promise<FrontendURLResult> {
63+
if (options.noTunnelUseLocalhost) {
64+
return {
65+
frontendUrl: 'https://localhost',
66+
frontendPort: options.port,
67+
usingLocalhost: true,
68+
}
69+
}
70+
6571
let frontendPort = 4040
6672
let frontendUrl = ''
6773
const usingLocalhost = options.noTunnelUseLocalhost
@@ -107,10 +113,7 @@ export async function generateFrontendURL(options: FrontendURLOptions): Promise<
107113
return {frontendUrl, frontendPort, usingLocalhost}
108114
}
109115

110-
if (options.noTunnelUseLocalhost) {
111-
frontendPort = options.port
112-
frontendUrl = 'https://localhost'
113-
} else if (options.tunnelClient) {
116+
if (options.tunnelClient) {
114117
frontendPort = options.tunnelClient.port
115118
frontendUrl = await pollTunnelURL(options.tunnelClient)
116119
}

packages/cli/oclif.manifest.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,7 @@
454454
"type": "option"
455455
},
456456
"localhost-port": {
457-
"dependsOn": [
458-
"use-localhost"
459-
],
460-
"description": "Port to use for localhost. Only applicable when --use-localhost is specified.",
457+
"description": "Port to use for localhost.",
461458
"env": "SHOPIFY_FLAG_LOCALHOST_PORT",
462459
"hasDynamicHelp": false,
463460
"hidden": true,

0 commit comments

Comments
 (0)