From 5ddf1188204b75bfd11f789374fca5c1c3577994 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 8 Sep 2025 21:00:44 +0100 Subject: [PATCH 1/4] Add host option to dev command and update related processes - Introduced a new `host` flag in the dev command to specify the network interface for the web server, defaulting to 'localhost'. - Updated the `DevOptions` interface to include the `host` property. - Modified the setup of proxy server processes to utilize the new `host` option. - Renamed `startProxyServer` to `proxyService` for clarity in the proxy server function. --- packages/app/src/cli/commands/app/dev.ts | 6 ++++++ packages/app/src/cli/services/dev.ts | 1 + .../dev/processes/setup-dev-processes.test.ts | 9 +++++++-- .../services/dev/processes/setup-dev-processes.ts | 13 ++++++++----- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/app/src/cli/commands/app/dev.ts b/packages/app/src/cli/commands/app/dev.ts index b73934b42c3..72a46c3b07e 100644 --- a/packages/app/src/cli/commands/app/dev.ts +++ b/packages/app/src/cli/commands/app/dev.ts @@ -87,6 +87,11 @@ If you're using the Ruby app template, then you need to complete the following s default: false, exclusive: ['tunnel-url'], }), + host: Flags.string({ + description: 'Set which network interface the web server listens on. The default value is localhost.', + env: 'SHOPIFY_FLAG_HOST', + default: 'localhost', + }), 'localhost-port': Flags.integer({ description: 'Port to use for localhost.', env: 'SHOPIFY_FLAG_LOCALHOST_PORT', @@ -174,6 +179,7 @@ If you're using the Ruby app template, then you need to complete the following s notify: flags.notify, graphiqlPort: flags['graphiql-port'], graphiqlKey: flags['graphiql-key'], + host: flags.host, tunnel: tunnelMode, } diff --git a/packages/app/src/cli/services/dev.ts b/packages/app/src/cli/services/dev.ts index af0af066266..be0853758de 100644 --- a/packages/app/src/cli/services/dev.ts +++ b/packages/app/src/cli/services/dev.ts @@ -69,6 +69,7 @@ export interface DevOptions { notify?: string graphiqlPort?: number graphiqlKey?: string + host: string } export async function dev(commandOptions: DevOptions) { diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index 12bfc659c4e..fcf72ce19bd 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -1,4 +1,4 @@ -import {DevConfig, setupDevProcesses, startProxyServer} from './setup-dev-processes.js' +import {DevConfig, setupDevProcesses, proxyService} from './setup-dev-processes.js' import {subscribeAndStartPolling} from './app-logs-polling.js' import {sendWebhook} from './uninstall-webhook.js' import {WebProcess, launchWebProcess} from './web.js' @@ -95,6 +95,7 @@ describe('setup-dev-processes', () => { commandConfig: new Config({root: ''}), skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, + host: 'localhost', } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', @@ -281,7 +282,7 @@ describe('setup-dev-processes', () => { expect(proxyServerProcess).toMatchObject({ type: 'proxy-server', prefix: 'proxy', - function: startProxyServer, + function: proxyService, options: { port: 444, localhostCert: { @@ -311,6 +312,7 @@ describe('setup-dev-processes', () => { commandConfig: new Config({root: ''}), skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, + host: 'localhost', } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', @@ -384,6 +386,7 @@ describe('setup-dev-processes', () => { commandConfig: new Config({root: ''}), skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, + host: 'localhost', } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', @@ -480,6 +483,7 @@ describe('setup-dev-processes', () => { commandConfig: new Config({root: ''}), skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, + host: 'localhost', } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', @@ -566,6 +570,7 @@ describe('setup-dev-processes', () => { commandConfig: new Config({root: ''}), skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, + host: 'localhost', } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', 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 593ce7188fa..bb40b9f1c90 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 @@ -30,6 +30,7 @@ interface ProxyServerProcess port: number rules: {[key: string]: string} localhostCert?: LocalhostCert + host: string }> { type: 'proxy-server' } @@ -251,11 +252,12 @@ async function setPortsAndAddProxyProcess( newProcesses.push({ type: 'proxy-server', prefix: 'proxy', - function: startProxyServer, + function: proxyService, options: { port: proxyPort, rules: allRules, localhostCert: reverseProxyCert, + host: commandOptions.host, }, }) } @@ -263,15 +265,16 @@ async function setPortsAndAddProxyProcess( return newProcesses } -export const startProxyServer: DevProcessFunction<{ +export const proxyService: DevProcessFunction<{ port: number rules: {[key: string]: string} localhostCert?: LocalhostCert -}> = async ({abortSignal, stdout}, {port, rules, localhostCert}) => { + host: string +}> = async ({abortSignal, stdout}, {port, rules, localhostCert, host}) => { const {server} = await getProxyingWebServer(rules, abortSignal, localhostCert, stdout) outputInfo( - `Proxy server started on port ${port} ${localhostCert ? `with certificate ${localhostCert.certPath}` : ''}`, + `Proxy server started on ${host}:${port} ${localhostCert ? `with certificate ${localhostCert.certPath}` : ''}`, stdout, ) - await server.listen(port, 'localhost') + await server.listen(port, host) } From 0275c2ea267db96e5ee5c3243c2c0677aa6fa3f0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 14 Sep 2025 09:01:17 +0100 Subject: [PATCH 2/4] Update setupDevProcesses to include commandOptions in proxy process configuration --- .../src/cli/services/dev/processes/setup-dev-processes.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 bb40b9f1c90..6c105b94975 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 @@ -202,7 +202,7 @@ export async function setupDevProcesses({ ].filter(stripUndefineds) // Add http server proxy & configure ports, for processes that need it - const processesWithProxy = await setPortsAndAddProxyProcess(processes, network.proxyPort, network.reverseProxyCert) + const processesWithProxy = await setPortsAndAddProxyProcess(processes, network.proxyPort, network.reverseProxyCert, commandOptions) return { processes: processesWithProxy, @@ -219,7 +219,8 @@ const stripUndefineds = (process: T | undefined | false): process is T => { async function setPortsAndAddProxyProcess( processes: DevProcesses, proxyPort: number, - reverseProxyCert?: LocalhostCert, + reverseProxyCert: LocalhostCert | undefined, + commandOptions: DevOptions, ): Promise { // Convert processes that use proxying to have a port number and register their mapping rules const processesAndRules = await Promise.all( From d863faa2ad1d72b597e89f5e1c7e045786c48f95 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 14 Sep 2025 09:19:33 +0100 Subject: [PATCH 3/4] Add host parameter to proxy server process configuration in setupDevProcesses test --- .../dev/processes/setup-dev-processes.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index fcf72ce19bd..06fd98ec224 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -289,6 +289,7 @@ describe('setup-dev-processes', () => { cert: 'cert', key: 'key', }, + host: 'localhost', rules: { '/extensions': `http://localhost:${previewExtensionPort}`, '/ping': `http://localhost:${hmrPort}`, @@ -299,6 +300,73 @@ describe('setup-dev-processes', () => { }) }) + test('proxy server process includes host parameter when configured for Docker', async () => { + // Given + const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({supportsDevSessions: false}) + const storeFqdn = 'store.myshopify.io' + const storeId = '123456789' + const remoteAppUpdated = true + const graphiqlPort = 1234 + const commandOptions: DevConfig['commandOptions'] = { + ...appContextResult, + commandConfig: new Config({root: ''}), + skipDependenciesInstallation: false, + tunnel: {mode: 'auto'}, + host: '0.0.0.0', // Docker host setting + } + const network: DevConfig['network'] = { + proxyUrl: 'https://example.com/proxy', + proxyPort: 444, + frontendPort: 3000, + backendPort: 3001, + currentUrls: { + applicationUrl: 'https://example.com/proxy', + redirectUrlWhitelist: ['https://example.com/proxy/auth/callback'], + }, + reverseProxyCert: { + cert: 'cert', + key: 'key', + certPath: 'path', + }, + } + + // Create simple app without theme extensions to avoid the theme API calls + const localApp = testAppWithConfig({ + app: testAppLinked({ + allExtensions: [await testUIExtension({type: 'web_pixel_extension'})], + webs: [{ + directory: 'web', + configuration: { + roles: [WebType.Backend, WebType.Frontend], + commands: {dev: 'npm exec remix dev'}, + webhooks_path: '/webhooks', + hmr_server: { + http_paths: ['/ping'], + }, + }, + }], + }), + }) + vi.spyOn(loader, 'reloadApp').mockResolvedValue(localApp) + + // When + const res = await setupDevProcesses({ + localApp, + remoteAppUpdated, + remoteApp: testOrganizationApp(), + developerPlatformClient, + storeFqdn, + storeId, + commandOptions, + network, + graphiqlPort, + }) + + // Then - Verify the proxy server process has the correct host setting + const proxyServerProcess = res.processes.find((process) => process.type === 'proxy-server') + expect(proxyServerProcess?.options.host).toBe('0.0.0.0') + }) + test('process list includes dev-session when useDevSession is true', async () => { const developerPlatformClient: DeveloperPlatformClient = testDeveloperPlatformClient({supportsDevSessions: true}) const storeFqdn = 'store.myshopify.io' From c5fae9c983055559caf7d66d58c41194a53d750f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 20 Sep 2025 14:58:08 +0100 Subject: [PATCH 4/4] Fix TypeScript compilation errors for Docker host binding feature - Add missing directory and update properties to DevOptions test objects - Add missing config property to TestAppWithConfigOptions calls - Add missing partnerUrlsUpdated property to DevConfig setupDevProcesses calls - Update CLI documentation and manifest for --host flag --- .../services/dev/processes/setup-dev-processes.test.ts | 6 +++++- packages/cli/README.md | 6 ++++-- packages/cli/oclif.manifest.json | 9 +++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index 06fd98ec224..eb1d0dd8ca1 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -313,6 +313,8 @@ describe('setup-dev-processes', () => { skipDependenciesInstallation: false, tunnel: {mode: 'auto'}, host: '0.0.0.0', // Docker host setting + directory: '', + update: false, } const network: DevConfig['network'] = { proxyUrl: 'https://example.com/proxy', @@ -332,6 +334,7 @@ describe('setup-dev-processes', () => { // Create simple app without theme extensions to avoid the theme API calls const localApp = testAppWithConfig({ + config: {}, app: testAppLinked({ allExtensions: [await testUIExtension({type: 'web_pixel_extension'})], webs: [{ @@ -359,6 +362,7 @@ describe('setup-dev-processes', () => { storeId, commandOptions, network, + partnerUrlsUpdated: true, graphiqlPort, }) @@ -392,7 +396,7 @@ describe('setup-dev-processes', () => { redirectUrlWhitelist: ['https://example.com/redirect'], }, } - const localApp = testAppWithConfig() + const localApp = testAppWithConfig({config: {}}) vi.spyOn(loader, 'reloadApp').mockResolvedValue(localApp) const remoteApp: DevConfig['remoteApp'] = { diff --git a/packages/cli/README.md b/packages/cli/README.md index f18d48887cb..b1c62d46e46 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -212,8 +212,8 @@ Run the app. ``` USAGE - $ shopify app dev [--checkout-cart-url ] [--client-id | -c ] [--localhost-port - ] [--no-color] [--no-update] [--notify ] [--path ] [--reset | ] + $ shopify app dev [--checkout-cart-url ] [--client-id | -c ] [--host ] + [--localhost-port ] [--no-color] [--no-update] [--notify ] [--path ] [--reset | ] [--skip-dependencies-installation] [-s ] [--subscription-product-url ] [-t ] [--theme-app-extension-port ] [--use-localhost | [--tunnel-url | ]] [--verbose] @@ -224,6 +224,8 @@ FLAGS --checkout-cart-url= Resource URL for checkout UI extension. Format: "/cart/{productVariantID}:{productQuantity}" --client-id= The Client ID of your app. + --host= [default: localhost] Set which network interface the web server listens on. + The default value is localhost. --localhost-port= Port to use for localhost. --no-color Disable color output. --no-update Skips the Partners Dashboard URL update step. diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index d2a5ab041de..971f4be6ace 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -459,6 +459,15 @@ "name": "graphiql-port", "type": "option" }, + "host": { + "default": "localhost", + "description": "Set which network interface the web server listens on. The default value is localhost.", + "env": "SHOPIFY_FLAG_HOST", + "hasDynamicHelp": false, + "multiple": false, + "name": "host", + "type": "option" + }, "localhost-port": { "description": "Port to use for localhost.", "env": "SHOPIFY_FLAG_LOCALHOST_PORT",