Skip to content
Merged
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/app-dev-port-validation.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd group all the changesets in a single file

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

`app dev` now shows a clear error when `--localhost-port`, `--theme-app-extension-port`, or `--graphiql-port` is given an invalid value. The port must be a number between 1 and 65535.
5 changes: 5 additions & 0 deletions .changeset/strong-pumas-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli-kit': minor
---

Add a reusable `--port` flag that validates the value is a number between 1 and 65535, for commands that accept a port.
5 changes: 5 additions & 0 deletions .changeset/theme-dev-port-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme': patch
---

`theme dev` now shows a clear error when `--port` is given an invalid value, instead of crashing. The port must be a number between 1 and 65535.
4 changes: 2 additions & 2 deletions docs-shopify.dev/commands/interfaces/app-dev.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface appdev {
'-c, --config <value>'?: string

/**
* Port to use for localhost.
* Port to use for localhost. Must be between 1 and 65535.
* @environment SHOPIFY_FLAG_LOCALHOST_PORT
*/
'--localhost-port <value>'?: string
Expand Down Expand Up @@ -83,7 +83,7 @@ export interface appdev {
'-t, --theme <value>'?: string

/**
* Local port of the theme app extension development server.
* Local port of the theme app extension development server. Must be between 1 and 65535.
* @environment SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT
*/
'--theme-app-extension-port <value>'?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export interface themedev {
'--path <value>'?: string

/**
* Local port to serve theme preview from.
* Local port to serve theme preview from. Must be between 1 and 65535.
* @environment SHOPIFY_FLAG_PORT
*/
'--port <value>'?: string
Expand Down
10 changes: 5 additions & 5 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,7 @@
"syntaxKind": "PropertySignature",
"name": "--localhost-port <value>",
"value": "string",
"description": "Port to use for localhost.",
"description": "Port to use for localhost. Must be between 1 and 65535.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_LOCALHOST_PORT"
},
Expand Down Expand Up @@ -939,7 +939,7 @@
"syntaxKind": "PropertySignature",
"name": "--theme-app-extension-port <value>",
"value": "string",
"description": "Local port of the theme app extension development server.",
"description": "Local port of the theme app extension development server. Must be between 1 and 65535.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT"
},
Expand Down Expand Up @@ -998,7 +998,7 @@
"environmentValue": "SHOPIFY_FLAG_THEME"
}
],
"value": "export interface appdev {\n /**\n * Resource URL for checkout UI extension. Format: \"/cart/{productVariantID}:{productQuantity}\"\n * @environment SHOPIFY_FLAG_CHECKOUT_CART_URL\n */\n '--checkout-cart-url <value>'?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Port to use for localhost.\n * @environment SHOPIFY_FLAG_LOCALHOST_PORT\n */\n '--localhost-port <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Uses the app URL from the toml file instead an autogenerated URL for dev.\n * @environment SHOPIFY_FLAG_NO_UPDATE\n */\n '--no-update'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * Skips the installation of dependencies. Deprecated, use workspaces instead.\n * @environment SHOPIFY_FLAG_SKIP_DEPENDENCIES_INSTALLATION\n */\n '--skip-dependencies-installation'?: ''\n\n /**\n * Store URL. Must be an existing development or Shopify Plus sandbox store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * Resource URL for subscription UI extension. Format: \"/products/{productId}\"\n * @environment SHOPIFY_FLAG_SUBSCRIPTION_PRODUCT_URL\n */\n '--subscription-product-url <value>'?: string\n\n /**\n * Theme ID or name of the theme app extension host theme.\n * @environment SHOPIFY_FLAG_THEME\n */\n '-t, --theme <value>'?: string\n\n /**\n * Local port of the theme app extension development server.\n * @environment SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT\n */\n '--theme-app-extension-port <value>'?: string\n\n /**\n * Use a custom tunnel, it must be running before executing dev. Format: \"https://my-tunnel-url:port\".\n * @environment SHOPIFY_FLAG_TUNNEL_URL\n */\n '--tunnel-url <value>'?: string\n\n /**\n * Service entry point will listen to localhost. A tunnel won't be used. Will work for testing many app features, but not those that directly invoke your app (E.g: Webhooks)\n * @environment SHOPIFY_FLAG_USE_LOCALHOST\n */\n '--use-localhost'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface appdev {\n /**\n * Resource URL for checkout UI extension. Format: \"/cart/{productVariantID}:{productQuantity}\"\n * @environment SHOPIFY_FLAG_CHECKOUT_CART_URL\n */\n '--checkout-cart-url <value>'?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Port to use for localhost. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_LOCALHOST_PORT\n */\n '--localhost-port <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Uses the app URL from the toml file instead an autogenerated URL for dev.\n * @environment SHOPIFY_FLAG_NO_UPDATE\n */\n '--no-update'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * Skips the installation of dependencies. Deprecated, use workspaces instead.\n * @environment SHOPIFY_FLAG_SKIP_DEPENDENCIES_INSTALLATION\n */\n '--skip-dependencies-installation'?: ''\n\n /**\n * Store URL. Must be an existing development or Shopify Plus sandbox store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * Resource URL for subscription UI extension. Format: \"/products/{productId}\"\n * @environment SHOPIFY_FLAG_SUBSCRIPTION_PRODUCT_URL\n */\n '--subscription-product-url <value>'?: string\n\n /**\n * Theme ID or name of the theme app extension host theme.\n * @environment SHOPIFY_FLAG_THEME\n */\n '-t, --theme <value>'?: string\n\n /**\n * Local port of the theme app extension development server. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT\n */\n '--theme-app-extension-port <value>'?: string\n\n /**\n * Use a custom tunnel, it must be running before executing dev. Format: \"https://my-tunnel-url:port\".\n * @environment SHOPIFY_FLAG_TUNNEL_URL\n */\n '--tunnel-url <value>'?: string\n\n /**\n * Service entry point will listen to localhost. A tunnel won't be used. Will work for testing many app features, but not those that directly invoke your app (E.g: Webhooks)\n * @environment SHOPIFY_FLAG_USE_LOCALHOST\n */\n '--use-localhost'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"appenvpull": {
Expand Down Expand Up @@ -4689,7 +4689,7 @@
"syntaxKind": "PropertySignature",
"name": "--port <value>",
"value": "string",
"description": "Local port to serve theme preview from.",
"description": "Local port to serve theme preview from. Must be between 1 and 65535.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_PORT"
},
Expand Down Expand Up @@ -4784,7 +4784,7 @@
"environmentValue": "SHOPIFY_FLAG_IGNORE"
}
],
"value": "export interface themedev {\n /**\n * Allow development on a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n \n * @environment SHOPIFY_FLAG_ERROR_OVERLAY\n */\n '--error-overlay <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Local port to serve theme preview from.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface themedev {\n /**\n * Allow development on a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Controls the visibility of the error overlay when an theme asset upload fails:\n- silent Prevents the error overlay from appearing.\n- default Displays the error overlay.\n \n * @environment SHOPIFY_FLAG_ERROR_OVERLAY\n */\n '--error-overlay <value>'?: string\n\n /**\n * Set which network interface the web server listens on. The default value is 127.0.0.1.\n * @environment SHOPIFY_FLAG_HOST\n */\n '--host <value>'?: string\n\n /**\n * Skip hot reloading any files that match the specified pattern.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing <value>'?: string\n\n /**\n * The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload\n * @environment SHOPIFY_FLAG_LIVE_RELOAD\n */\n '--live-reload <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevents files from being deleted in the remote theme when a file has been deleted locally. This applies to files that are deleted while the command is running, and files that have been deleted locally before the command is run.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify <value>'?: string\n\n /**\n * Hot reload only files that match the specified pattern.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <value>'?: string\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Local port to serve theme preview from. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_PORT\n */\n '--port <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Synchronize Theme Editor updates in the local theme files.\n * @environment SHOPIFY_FLAG_THEME_EDITOR_SYNC\n */\n '--theme-editor-sync'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"themeduplicate": {
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/cli/commands/app/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {storeContext} from '../../services/store-context.js'
import {getTunnelMode} from '../../services/dev/tunnel-mode.js'
import {Flags} from '@oclif/core'
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {globalFlags, portFlag} from '@shopify/cli-kit/node/cli'
import {addPublicMetadata} from '@shopify/cli-kit/node/metadata'

export default class Dev extends AppLinkedCommand {
Expand Down Expand Up @@ -57,7 +57,7 @@ export default class Dev extends AppLinkedCommand {
default: false,
exclusive: ['tunnel-url'],
}),
'localhost-port': Flags.integer({
'localhost-port': portFlag({
description: 'Port to use for localhost.',
env: 'SHOPIFY_FLAG_LOCALHOST_PORT',
}),
Expand All @@ -66,7 +66,7 @@ export default class Dev extends AppLinkedCommand {
description: 'Theme ID or name of the theme app extension host theme.',
env: 'SHOPIFY_FLAG_THEME',
}),
'theme-app-extension-port': Flags.integer({
'theme-app-extension-port': portFlag({
description: 'Local port of the theme app extension development server.',
env: 'SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT',
}),
Expand All @@ -75,7 +75,7 @@ export default class Dev extends AppLinkedCommand {
'The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.',
env: 'SHOPIFY_FLAG_NOTIFY',
}),
'graphiql-port': Flags.integer({
'graphiql-port': portFlag({
hidden: true,
description: 'Local port of the GraphiQL development server.',
env: 'SHOPIFY_FLAG_GRAPHIQL_PORT',
Expand Down
15 changes: 14 additions & 1 deletion packages/cli-kit/src/public/node/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {clearCache, runCLI, runCreateCLI} from './cli.js'
import {clearCache, runCLI, runCreateCLI, portFlag} from './cli.js'
import {findUpAndReadPackageJson} from './node-package-manager.js'
import {mockAndCaptureOutput} from './testing/output.js'
import * as confStore from '../../private/node/conf-store.js'
Expand Down Expand Up @@ -135,3 +135,16 @@ describe('clearCache', () => {
spy.mockRestore()
})
})

describe('portFlag', () => {
const flag = portFlag()
test('parses a valid port to a number', async () => {
await expect(flag.parse('9292', {} as any, flag as any)).resolves.toBe(9292)
})
test.each(['13245574', '65536', '0', '-1', 'abc', '92.5', '', '0x10', '1e2', ' 9293 ', '+9292'])(
'rejects invalid port %s',
async (input) => {
await expect(flag.parse(input, {} as any, flag as any)).rejects.toThrowError(/Expected an integer/)
},
)
})
13 changes: 13 additions & 0 deletions packages/cli-kit/src/public/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,19 @@ export const jsonFlag = {
}),
}

/**
* Builds a `--port` flag that only accepts a valid port number. The flag parses its
* value as an integer and rejects anything that isn't a whole number between 1 and
* 65535, so commands fail with a clear message instead of crashing on an out-of-range
* port. The accepted range is appended to the description so it shows up in `--help`.
* @param options - Optional overrides for the flag's description, environment variable, and visibility.
* @returns An oclif integer flag constrained to the valid port range.
*/
export const portFlag = (options: {description?: string; env?: string; hidden?: boolean} = {}) => {
const description = [options.description, 'Must be between 1 and 65535.'].filter(Boolean).join(' ')
return Flags.integer({min: 1, max: 65535, ...options, description})
Comment on lines +164 to +166
}

/**
* Clear the CLI cache, used to store some API responses and handle notifications status
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-kit/src/public/node/tcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,8 @@ describe('checkPortAvailability', () => {
server.close()
}
})

test.each([13245574, 70000, -1, NaN])('resolves false for out-of-range/invalid port %s', async (port) => {
await expect(checkPortAvailability(port)).resolves.toBe(false)
})
})
Loading
Loading