Skip to content
Open
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-non-interactive-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Add non-interactive `app dev` options: `--store-password` and `--install-mkcert`.
12 changes: 12 additions & 0 deletions docs-shopify.dev/commands/interfaces/app-dev.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export interface appdev {
*/
'-c, --config <value>'?: string

/**
* Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.
* @environment SHOPIFY_FLAG_INSTALL_MKCERT
*/
'--install-mkcert'?: ''

/**
* Port to use for localhost. Must be between 1 and 65535.
* @environment SHOPIFY_FLAG_LOCALHOST_PORT
Expand Down Expand Up @@ -70,6 +76,12 @@ export interface appdev {
*/
'-s, --store <value>'?: string

/**
* The password for storefronts with password protection.
* @environment SHOPIFY_FLAG_STORE_PASSWORD
*/
'--store-password <value>'?: string

/**
* Resource URL for subscription UI extension. Format: "/products/{productId}"
* @environment SHOPIFY_FLAG_SUBSCRIPTION_PRODUCT_URL
Expand Down
20 changes: 19 additions & 1 deletion docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,15 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_CLIENT_ID"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--install-mkcert",
"value": "''",
"description": "Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_INSTALL_MKCERT"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -943,6 +952,15 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_SKIP_DEPENDENCIES_INSTALLATION"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--store-password <value>",
"value": "string",
"description": "The password for storefronts with password protection.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_PASSWORD"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -1016,7 +1034,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. 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}"
"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 * Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.\n * @environment SHOPIFY_FLAG_INSTALL_MKCERT\n */\n '--install-mkcert'?: ''\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 * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password <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

This file was deleted.

12 changes: 12 additions & 0 deletions packages/app/src/cli/commands/app/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ export default class Dev extends AppLinkedCommand {
default: false,
exclusive: ['tunnel-url'],
}),
'install-mkcert': Flags.boolean({
description:
'Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.',
env: 'SHOPIFY_FLAG_INSTALL_MKCERT',
default: false,
}),
'localhost-port': portFlag({
description: 'Port to use for localhost.',
env: 'SHOPIFY_FLAG_LOCALHOST_PORT',
Expand All @@ -70,6 +76,10 @@ export default class Dev extends AppLinkedCommand {
description: 'Local port of the theme app extension development server.',
env: 'SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT',
}),
'store-password': Flags.string({
description: 'The password for storefronts with password protection.',
env: 'SHOPIFY_FLAG_STORE_PASSWORD',
}),
notify: Flags.string({
description:
'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.',
Expand Down Expand Up @@ -134,9 +144,11 @@ export default class Dev extends AppLinkedCommand {
checkoutCartUrl: flags['checkout-cart-url'],
theme: flags.theme,
themeExtensionPort: flags['theme-app-extension-port'],
storePassword: flags['store-password'],
notify: flags.notify,
graphiqlPort: flags['graphiql-port'],
graphiqlKey: flags['graphiql-key'],
installMkcert: flags['install-mkcert'],
tunnel: tunnelMode,
}

Expand Down
10 changes: 0 additions & 10 deletions packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import {
import {AllAppExtensionRegistrationsQuerySchema} from '../../api/graphql/all_app_extension_registrations.js'
import {AppDeploySchema, AppDeployVariables} from '../../api/graphql/app_deploy.js'
import {ExtensionCreateSchema, ExtensionCreateVariables} from '../../api/graphql/extension_create.js'
import {ConvertDevToTransferDisabledStoreVariables} from '../../api/graphql/convert_dev_to_transfer_disabled_store.js'
import {
DevelopmentStorePreviewUpdateInput,
DevelopmentStorePreviewUpdateSchema,
Expand Down Expand Up @@ -1275,13 +1274,6 @@ const generateSignedUploadUrlResponse: AssetUrlSchema = {
userErrors: [],
}

const convertedToTransferDisabledStoreResponse = {
convertDevToTestStore: {
convertedToTestStore: true,
userErrors: [],
},
}

const updateDeveloperPreviewResponse: DevelopmentStorePreviewUpdateSchema = {
developmentStorePreviewUpdate: {
app: {
Expand Down Expand Up @@ -1395,8 +1387,6 @@ export function testDeveloperPlatformClient(stubs: Partial<DeveloperPlatformClie
deploy: (_input: AppDeployVariables) => Promise.resolve(deployResponse),
release: (_input: {app: MinimalAppIdentifiers; version: AppVersionIdentifiers}) => Promise.resolve(releaseResponse),
generateSignedUploadUrl: (_app: MinimalAppIdentifiers) => Promise.resolve(generateSignedUploadUrlResponse),
convertToTransferDisabledStore: (_input: ConvertDevToTransferDisabledStoreVariables) =>
Promise.resolve(convertedToTransferDisabledStoreResponse),
updateDeveloperPreview: (_input: DevelopmentStorePreviewUpdateInput) =>
Promise.resolve(updateDeveloperPreviewResponse),
appPreviewMode: (_input: FindAppPreviewModeVariables) => Promise.resolve(appPreviewModeResponse),
Expand Down
9 changes: 0 additions & 9 deletions packages/app/src/cli/prompts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,6 @@ export async function selectStorePrompt({
return currentStores.find((store) => store.shopId === id)
}

export async function confirmConversionToTransferDisabledStorePrompt(): Promise<boolean> {
return renderConfirmationPrompt({
message: `Make this store transfer-disabled? For security, once you use a dev store to preview an app locally, the store can never be transferred to a merchant to use as a production store.`,
confirmationMessage: 'Yes, make this store transfer-disabled permanently',
cancellationMessage: 'No, select another store',
defaultValue: false,
})
}

export async function appNamePrompt(currentName: string): Promise<string> {
return renderTextPrompt({
message: 'App name',
Expand Down
19 changes: 19 additions & 0 deletions packages/app/src/cli/services/app/config/link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,25 @@ describe('link', () => {
})
})

test('when remote app exists and supports dev sessions then include automatically_update_urls_on_dev = true', async () => {
await inTemporaryDirectory(async (tmp) => {
// Given
const developerPlatformClient = buildDeveloperPlatformClient({supportsDevSessions: true})
const options: LinkOptions = {
directory: tmp,
developerPlatformClient,
}
await mockLoadOpaqueAppWithApp(tmp)
vi.mocked(fetchOrCreateOrganizationApp).mockResolvedValue(mockRemoteApp({developerPlatformClient}))

// When
const {configuration} = await link(options)

// Then
expect(configuration.build?.automatically_update_urls_on_dev).toBe(true)
})
})

test('replace arrays content with the remote one', async () => {
await inTemporaryDirectory(async (tmp) => {
// Given
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/cli/services/app/config/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ function buildOptionsForGeneratedConfigFile(options: {
} = options
const buildOptions = {
...(linkedAppWasNewlyCreated ? {include_config_on_deploy: true} : {}),
...(defaultToUpdateUrlsOnDev && linkedAppWasNewlyCreated ? {automatically_update_urls_on_dev: true} : {}),
...(defaultToUpdateUrlsOnDev ? {automatically_update_urls_on_dev: true} : {}),
...(linkedAppAndClientIdFromFileAreInSync ? existingBuildOptions : {}),
}
if (isEmpty(buildOptions)) {
Expand Down
5 changes: 5 additions & 0 deletions packages/app/src/cli/services/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ export interface DevOptions {
tunnel: TunnelMode
theme?: string
themeExtensionPort?: number
storePassword?: string
notify?: string
graphiqlPort?: number
graphiqlKey?: string
installMkcert?: boolean
}

export async function dev(commandOptions: DevOptions) {
Expand Down Expand Up @@ -152,6 +154,7 @@ async function prepareForDev(commandOptions: DevOptions): Promise<DevConfig> {
tunnel,
tunnelClient,
remoteApp.configuration,
commandOptions.installMkcert,
)
app.webs = webs

Expand Down Expand Up @@ -324,6 +327,7 @@ async function setupNetworkingOptions(
tunnelOptions: TunnelMode,
tunnelClient?: TunnelClient,
remoteAppConfig?: AppConfigurationUsedByCli,
installMkcert?: boolean,
) {
const {backendConfig, frontendConfig} = frontAndBackendConfig(webs)

Expand Down Expand Up @@ -361,6 +365,7 @@ async function setupNetworkingOptions(
if (tunnelOptions.mode === 'use-localhost') {
const {keyContent, certContent, certPath} = await generateCertificate({
appDirectory,
install: installMkcert,
})

reverseProxyCert = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export async function setupDevProcesses({
storeFqdn,
theme: commandOptions.theme,
themeExtensionPort: commandOptions.themeExtensionPort,
storePassword: commandOptions.storePassword,
}),
setupSendUninstallWebhookProcess({
webs: reloadedApp.webs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,24 @@ describe('setupPreviewThemeAppExtensionsProcess', () => {
expect(ensureValidPassword).toHaveBeenCalledWith(undefined, 'test.myshopify.com')
expect(result!.options.storefrontPassword).toEqual(storefrontPassword)
})

test('uses the provided store password when one is required', async () => {
const mockTheme = {id: 123} as Theme
vi.mocked(fetchTheme).mockResolvedValue(mockTheme)
vi.mocked(isStorefrontPasswordProtected).mockResolvedValue(true)
vi.mocked(ensureValidPassword).mockResolvedValue('validated-password')

const result = await setupPreviewThemeAppExtensionsProcess({
localApp: testApp({allExtensions: [await testThemeExtensions()]}),
remoteApp: testOrganizationApp(),
storeFqdn: 'test.myshopify.com',
theme: '1',
storePassword: 'provided-password',
})

expect(ensureValidPassword).toHaveBeenCalledWith('provided-password', 'test.myshopify.com')
expect(result!.options.storefrontPassword).toEqual('validated-password')
})
})

describe('findOrCreateHostTheme', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface HostThemeSetupOptions {
storeFqdn: string
theme?: string
themeExtensionPort?: number
storePassword?: string
}

export interface PreviewThemeAppExtensionsProcess extends BaseProcess<ThemeAppExtensionServerOptions> {
Expand Down Expand Up @@ -53,7 +54,7 @@ export async function setupPreviewThemeAppExtensionsProcess(

const storeFqdn = adminSession.storeFqdn
const storefrontPassword = (await isStorefrontPasswordProtected(adminSession))
? await ensureValidPassword(undefined, storeFqdn)
? await ensureValidPassword(options.storePassword, storeFqdn)
: undefined

const theme = await findOrCreateHostTheme(adminSession, options.theme)
Expand Down
Loading
Loading