From a0172e4dfb0868de8f166473423494d06cefe164 Mon Sep 17 00:00:00 2001 From: Elana Kopelevich Date: Fri, 3 Apr 2026 17:19:08 -0600 Subject: [PATCH 1/2] Remove HOSTED_APPS env var and make extension-only template the default The new shopify-app-template-extension-only template was previously gated behind the HOSTED_APPS env var (PR #7096). This removes the env var entirely and makes the hosted app template the default for the "none" (extension-only) option in `shopify app init`. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/src/cli/prompts/init/init.test.ts | 50 ++++--------------- packages/app/src/cli/prompts/init/init.ts | 19 +++---- .../cli-kit/src/private/node/constants.ts | 1 - .../src/public/node/context/local.test.ts | 25 ---------- .../cli-kit/src/public/node/context/local.ts | 10 ---- 5 files changed, 16 insertions(+), 89 deletions(-) diff --git a/packages/app/src/cli/prompts/init/init.test.ts b/packages/app/src/cli/prompts/init/init.test.ts index af3ee97d02..c28c8e3043 100644 --- a/packages/app/src/cli/prompts/init/init.test.ts +++ b/packages/app/src/cli/prompts/init/init.test.ts @@ -1,12 +1,10 @@ -import init, {buildNoneTemplate, InitOptions, visibleTemplates} from './init.js' +import init, {InitOptions} from './init.js' import {describe, expect, vi, test, beforeEach} from 'vitest' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' import {installGlobalCLIPrompt} from '@shopify/cli-kit/node/is-global' -import {isHostedAppsMode} from '@shopify/cli-kit/node/context/local' vi.mock('@shopify/cli-kit/node/ui') vi.mock('@shopify/cli-kit/node/is-global') -vi.mock('@shopify/cli-kit/node/context/local') const globalCLIResult = {install: true, alreadyInstalled: false} @@ -17,7 +15,7 @@ describe('init', () => { test('it renders the label for the template options', async () => { const answers = { - template: 'https://github.com/Shopify/shopify-app-template-none', + template: 'https://github.com/Shopify/shopify-app-template-extension-only', } const options: InitOptions = {} @@ -31,7 +29,10 @@ describe('init', () => { expect(renderSelectPrompt).toHaveBeenCalledWith({ choices: [ {label: 'Build a React Router app (recommended)', value: 'reactRouter'}, - {label: 'Build an extension-only app', value: 'none'}, + { + label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', + value: 'none', + }, ], message: 'Get started building your app:', defaultValue: 'reactRouter', @@ -39,40 +40,6 @@ describe('init', () => { expect(got).toEqual({...options, ...answers, templateType: 'none', globalCLIResult}) }) - describe('visibleTemplates', () => { - test('omits the deprecated remix template so it is not advertised in --template help text', () => { - expect(visibleTemplates).not.toContain('remix') - }) - - test('exposes only the templates that are still actively offered', () => { - expect(visibleTemplates).toEqual(['reactRouter', 'none']) - }) - }) - - describe('buildNoneTemplate', () => { - test('returns extension-only template URL when HOSTED_APPS is enabled', () => { - // Given - vi.mocked(isHostedAppsMode).mockReturnValue(true) - - // When - const got = buildNoneTemplate() - - // Then - expect(got.url).toBe('https://github.com/Shopify/shopify-app-template-extension-only') - }) - - test('returns default template URL when HOSTED_APPS is not set', () => { - // Given - vi.mocked(isHostedAppsMode).mockReturnValue(false) - - // When - const got = buildNoneTemplate() - - // Then - expect(got.url).toBe('https://github.com/Shopify/shopify-app-template-none') - }) - }) - test('it renders branches for templates that have them', async () => { const answers = { template: 'https://github.com/Shopify/shopify-app-template-react-router#javascript-cli', @@ -90,7 +57,10 @@ describe('init', () => { expect(renderSelectPrompt).toHaveBeenCalledWith({ choices: [ {label: 'Build a React Router app (recommended)', value: 'reactRouter'}, - {label: 'Build an extension-only app', value: 'none'}, + { + label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', + value: 'none', + }, ], message: 'Get started building your app:', defaultValue: 'reactRouter', diff --git a/packages/app/src/cli/prompts/init/init.ts b/packages/app/src/cli/prompts/init/init.ts index 5b840ba005..8bd5b1d8ce 100644 --- a/packages/app/src/cli/prompts/init/init.ts +++ b/packages/app/src/cli/prompts/init/init.ts @@ -1,5 +1,4 @@ import {InstallGlobalCLIPromptResult, installGlobalCLIPrompt} from '@shopify/cli-kit/node/is-global' -import {isHostedAppsMode} from '@shopify/cli-kit/node/context/local' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' export interface InitOptions { @@ -29,16 +28,6 @@ interface Template { } } -export function buildNoneTemplate(): Template { - return { - url: isHostedAppsMode() - ? 'https://github.com/Shopify/shopify-app-template-extension-only' - : 'https://github.com/Shopify/shopify-app-template-none', - label: 'Build an extension-only app', - visible: true, - } -} - // Eventually this list should be taken from a remote location // That way we don't have to update the CLI every time we add a template export const templates = { @@ -68,7 +57,11 @@ export const templates = { }, }, } as Template, - none: buildNoneTemplate(), + none: { + url: 'https://github.com/Shopify/shopify-app-template-extension-only', + label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', + visible: true, + } as Template, node: { url: 'https://github.com/Shopify/shopify-app-template-node', visible: false, @@ -77,7 +70,7 @@ export const templates = { url: 'https://github.com/Shopify/shopify-app-template-ruby', visible: false, } as Template, -} +} as const type PredefinedTemplate = keyof typeof templates const allTemplates = Object.keys(templates) as Readonly diff --git a/packages/cli-kit/src/private/node/constants.ts b/packages/cli-kit/src/private/node/constants.ts index aac03a2f4c..a38e83f2d9 100644 --- a/packages/cli-kit/src/private/node/constants.ts +++ b/packages/cli-kit/src/private/node/constants.ts @@ -49,7 +49,6 @@ export const environmentVariables = { skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY', maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS', disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING', - hostedApps: 'HOSTED_APPS', } export const defaultThemeKitAccessDomain = 'theme-kit-access.shopifyapps.com' diff --git a/packages/cli-kit/src/public/node/context/local.test.ts b/packages/cli-kit/src/public/node/context/local.test.ts index fa10914777..abe71bb52b 100644 --- a/packages/cli-kit/src/public/node/context/local.test.ts +++ b/packages/cli-kit/src/public/node/context/local.test.ts @@ -2,7 +2,6 @@ import { ciPlatform, hasGit, isDevelopment, - isHostedAppsMode, isShopify, isTerminalInteractive, isUnitTest, @@ -97,30 +96,6 @@ describe('isDevelopment', () => { }) }) -describe('isHostedAppsMode', () => { - test('returns true when HOSTED_APPS is truthy', () => { - // Given - const env = {HOSTED_APPS: '1'} - - // When - const got = isHostedAppsMode(env) - - // Then - expect(got).toBe(true) - }) - - test('returns false when HOSTED_APPS is not set', () => { - // Given - const env = {} - - // When - const got = isHostedAppsMode(env) - - // Then - expect(got).toBe(false) - }) -}) - describe('isShopify', () => { test('returns false when the SHOPIFY_RUN_AS_USER env. variable is truthy', async () => { // Given diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index 2388db690d..bd902c9a33 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -54,16 +54,6 @@ export function isVerbose(env = process.env): boolean { return isTruthy(env[environmentVariables.verbose]) || process.argv.includes('--verbose') } -/** - * Returns true if the hosted apps mode is enabled. - * - * @param env - The environment variables from the environment of the current process. - * @returns True if HOSTED_APPS is truthy. - */ -export function isHostedAppsMode(env = process.env): boolean { - return isTruthy(env[environmentVariables.hostedApps]) -} - /** * Returns true if the environment in which the CLI is running is either * a local environment (where dev is present). From 81af9eb2c82259529b2d347b584b2636c96a032f Mon Sep 17 00:00:00 2001 From: Mitch Lillie Date: Tue, 28 Apr 2026 10:54:57 -0700 Subject: [PATCH 2/2] Remove custom label for extension only apps --- .changeset/ninety-doodles-scream.md | 6 ++++++ .../app/src/cli/prompts/init/init.test.ts | 19 +++++++++++++------ packages/app/src/cli/prompts/init/init.ts | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 .changeset/ninety-doodles-scream.md diff --git a/.changeset/ninety-doodles-scream.md b/.changeset/ninety-doodles-scream.md new file mode 100644 index 0000000000..ec3bd6005c --- /dev/null +++ b/.changeset/ninety-doodles-scream.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli-kit': minor +'@shopify/app': minor +--- + +Update extension-only template to include app home by default diff --git a/packages/app/src/cli/prompts/init/init.test.ts b/packages/app/src/cli/prompts/init/init.test.ts index c28c8e3043..a4a0644475 100644 --- a/packages/app/src/cli/prompts/init/init.test.ts +++ b/packages/app/src/cli/prompts/init/init.test.ts @@ -1,4 +1,4 @@ -import init, {InitOptions} from './init.js' +import init, {InitOptions, visibleTemplates} from './init.js' import {describe, expect, vi, test, beforeEach} from 'vitest' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' import {installGlobalCLIPrompt} from '@shopify/cli-kit/node/is-global' @@ -29,10 +29,7 @@ describe('init', () => { expect(renderSelectPrompt).toHaveBeenCalledWith({ choices: [ {label: 'Build a React Router app (recommended)', value: 'reactRouter'}, - { - label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', - value: 'none', - }, + {label: 'Build an extension-only app', value: 'none'}, ], message: 'Get started building your app:', defaultValue: 'reactRouter', @@ -40,6 +37,16 @@ describe('init', () => { expect(got).toEqual({...options, ...answers, templateType: 'none', globalCLIResult}) }) + describe('visibleTemplates', () => { + test('omits the deprecated remix template so it is not advertised in --template help text', () => { + expect(visibleTemplates).not.toContain('remix') + }) + + test('exposes only the templates that are still actively offered', () => { + expect(visibleTemplates).toEqual(['reactRouter', 'none']) + }) + }) + test('it renders branches for templates that have them', async () => { const answers = { template: 'https://github.com/Shopify/shopify-app-template-react-router#javascript-cli', @@ -58,7 +65,7 @@ describe('init', () => { choices: [ {label: 'Build a React Router app (recommended)', value: 'reactRouter'}, { - label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', + label: 'Build an extension-only app', value: 'none', }, ], diff --git a/packages/app/src/cli/prompts/init/init.ts b/packages/app/src/cli/prompts/init/init.ts index 8bd5b1d8ce..dc9d1c51e3 100644 --- a/packages/app/src/cli/prompts/init/init.ts +++ b/packages/app/src/cli/prompts/init/init.ts @@ -59,7 +59,7 @@ export const templates = { } as Template, none: { url: 'https://github.com/Shopify/shopify-app-template-extension-only', - label: 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)', + label: 'Build an extension-only app', visible: true, } as Template, node: {