From 64d1fc7e358539536f2578f4d230c2a37931e71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20L=C3=B3pez=20Vald=C3=A9s?= Date: Mon, 3 Nov 2025 12:56:51 +0100 Subject: [PATCH] Fix how app name is set from the AppLoader --- .changeset/happy-jobs-type.md | 6 +++ .../app/src/cli/models/app/loader.test.ts | 50 ++++++++++++++++++- packages/app/src/cli/models/app/loader.ts | 11 ++-- .../specifications/types/app_config.ts | 1 + .../app/src/cli/services/app-context.test.ts | 39 +++++++++++---- 5 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 .changeset/happy-jobs-type.md diff --git a/.changeset/happy-jobs-type.md b/.changeset/happy-jobs-type.md new file mode 100644 index 00000000000..b700bc77e46 --- /dev/null +++ b/.changeset/happy-jobs-type.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli-kit': minor +'@shopify/app': minor +--- + +Use the handle to set the app version name, and the TOML name as a fallback diff --git a/packages/app/src/cli/models/app/loader.test.ts b/packages/app/src/cli/models/app/loader.test.ts index b77a9356f9a..dabf0233460 100644 --- a/packages/app/src/cli/models/app/loader.test.ts +++ b/packages/app/src/cli/models/app/loader.test.ts @@ -64,6 +64,7 @@ describe('load', () => { } const appConfiguration = ` +name = "my_app" scopes = "read_products" ` const linkedAppConfiguration = ` @@ -264,7 +265,54 @@ wrong = "property" const app = await loadApp({directory: tmpDir, specifications: [], userProvidedConfigName: undefined}) // Then - expect(app.name).toBe('my_app') + expect(app.name).toBe('for-testing') + }) + + test('uses handle from configuration as app name when present', async () => { + // Given + const appConfiguration = ` +name = "display-name" +handle = "app-handle" +client_id = "1234567890" +application_url = "https://example.com/lala" +embedded = true + +[webhooks] +api_version = "2023-07" + +[auth] +redirect_urls = [ "https://example.com/api/auth" ] +` + await writeConfig(appConfiguration) + + // When + const app = await loadTestingApp() + + // Then + expect(app.name).toBe('app-handle') + }) + + test('uses name from configuration when handle is not present', async () => { + // Given + const appConfiguration = ` +name = "config-name" +client_id = "1234567890" +application_url = "https://example.com/lala" +embedded = true + +[webhooks] +api_version = "2023-07" + +[auth] +redirect_urls = [ "https://example.com/api/auth" ] +` + await writeConfig(appConfiguration) + + // When + const app = await loadTestingApp() + + // Then + expect(app.name).toBe('config-name') }) test('defaults to npm as the package manager when the configuration is valid', async () => { diff --git a/packages/app/src/cli/models/app/loader.ts b/packages/app/src/cli/models/app/loader.ts index 3cc7e464f9f..05536b78d4c 100644 --- a/packages/app/src/cli/models/app/loader.ts +++ b/packages/app/src/cli/models/app/loader.ts @@ -42,7 +42,6 @@ import {readAndParseDotEnv, DotEnvFile} from '@shopify/cli-kit/node/dot-env' import { getDependencies, getPackageManager, - getPackageName, usesWorkspaces as appUsesWorkspaces, } from '@shopify/cli-kit/node/node-package-manager' import {resolveFramework} from '@shopify/cli-kit/node/framework' @@ -343,7 +342,10 @@ class AppLoader { - const packageJSONPath = joinPath(appDirectory, 'package.json') - return (await getPackageName(packageJSONPath)) ?? basename(appDirectory) -} - async function getProjectType(webs: Web[]): Promise<'node' | 'php' | 'ruby' | 'frontend' | undefined> { const backendWebs = webs.filter((web) => isWebType(web, WebType.Backend)) const frontendWebs = webs.filter((web) => isWebType(web, WebType.Frontend)) diff --git a/packages/app/src/cli/models/extensions/specifications/types/app_config.ts b/packages/app/src/cli/models/extensions/specifications/types/app_config.ts index a0b154f18df..0ba266ecb6d 100644 --- a/packages/app/src/cli/models/extensions/specifications/types/app_config.ts +++ b/packages/app/src/cli/models/extensions/specifications/types/app_config.ts @@ -12,6 +12,7 @@ import {WebhooksConfig} from './app_config_webhook.js' */ export interface AppConfigurationUsedByCli { name: string + handle?: string application_url: string embedded: boolean app_proxy?: { diff --git a/packages/app/src/cli/services/app-context.test.ts b/packages/app/src/cli/services/app-context.test.ts index d2345a89359..37ffaf956cc 100644 --- a/packages/app/src/cli/services/app-context.test.ts +++ b/packages/app/src/cli/services/app-context.test.ts @@ -49,7 +49,9 @@ describe('linkedAppContext', () => { test('returns linked app context when app is already linked', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) // When @@ -65,6 +67,7 @@ describe('linkedAppContext', () => { app: expect.objectContaining({ configuration: { client_id: 'test-api-key', + name: 'test-app', path: normalizePath(joinPath(tmp, 'shopify.app.toml')), }, }), @@ -98,7 +101,10 @@ describe('linkedAppContext', () => { configurationPath: `${tmp}/shopify.app.stg.toml`, configSource: 'cached', configurationFileName: 'shopify.app.stg.toml', - basicConfiguration: {client_id: 'test-api-key', path: normalizePath(joinPath(tmp, 'shopify.app.stg.toml'))}, + basicConfiguration: { + client_id: 'test-api-key', + path: normalizePath(joinPath(tmp, 'shopify.app.stg.toml')), + }, }, configuration: { client_id: 'test-api-key', @@ -133,7 +139,9 @@ describe('linkedAppContext', () => { await inTemporaryDirectory(async (tmp) => { // Given vi.mocked(appFromIdentifiers).mockResolvedValue({...mockRemoteApp, apiKey: 'test-api-key-new'}) - const content = `client_id="test-api-key-new"` + const content = ` +name = "test-app" +client_id="test-api-key-new"` await writeAppConfig(tmp, content) localStorage.setCachedAppInfo({ appId: 'test-api-key-old', @@ -166,7 +174,9 @@ describe('linkedAppContext', () => { test('uses provided clientId when available and updates the app configuration', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) const newClientId = 'new-api-key' @@ -191,7 +201,9 @@ describe('linkedAppContext', () => { test('resets app when there is a valid toml but reset option is true', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) vi.mocked(link).mockResolvedValue({ @@ -202,7 +214,10 @@ describe('linkedAppContext', () => { configurationPath: `${tmp}/shopify.app.toml`, configSource: 'cached', configurationFileName: 'shopify.app.toml', - basicConfiguration: {client_id: 'test-api-key', path: normalizePath(joinPath(tmp, 'shopify.app.toml'))}, + basicConfiguration: { + client_id: 'test-api-key', + path: normalizePath(joinPath(tmp, 'shopify.app.toml')), + }, }, configuration: { client_id: 'test-api-key', @@ -229,7 +244,9 @@ describe('linkedAppContext', () => { test('logs metadata', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) // When @@ -255,7 +272,9 @@ describe('linkedAppContext', () => { test('uses unsafeReportMode when provided', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) const loadSpy = vi.spyOn(loader, 'loadAppUsingConfigurationState') @@ -278,7 +297,9 @@ describe('linkedAppContext', () => { test('does not use unsafeReportMode when not provided', async () => { await inTemporaryDirectory(async (tmp) => { // Given - const content = `client_id="test-api-key"` + const content = ` +name = "test-app" +client_id="test-api-key"` await writeAppConfig(tmp, content) const loadSpy = vi.spyOn(loader, 'loadAppUsingConfigurationState')