Skip to content

Commit

Permalink
Merge pull request #5193 from Shopify/01-14-closes_https__github.com_…
Browse files Browse the repository at this point in the history
…shopify_develop-app-inner-loop_issues_2460

closes: Shopify/develop-app-inner-loop#2460
  • Loading branch information
alexanderMontague authored Jan 15, 2025
2 parents 1a922a6 + 5156a87 commit 14612d3
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 32 deletions.
112 changes: 86 additions & 26 deletions packages/app/src/cli/services/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import {fetchOrganizations, fetchOrgFromId} from './dev/fetch.js'
import {selectOrCreateApp} from './dev/select-app.js'
import {selectStore} from './dev/select-store.js'
import {ensureDeploymentIdsPresence} from './context/identifiers.js'
import {ensureDeployContext, ensureThemeExtensionDevContext} from './context.js'
import {appFromIdentifiers, ensureDeployContext, ensureThemeExtensionDevContext} from './context.js'
import {createExtension} from './dev/create-extension.js'
import {CachedAppInfo} from './local-storage.js'
import link from './app/config/link.js'
import {fetchSpecifications} from './generate/fetch-extension-specifications.js'
import * as patchAppConfigurationFile from './app/patch-app-configuration-file.js'
import {DeployOptions} from './deploy.js'
import {isServiceAccount, isUserAccount} from './context/partner-account-info.js'
import {
MinimalAppIdentifiers,
AppApiKeyAndOrgId,
Expand All @@ -22,15 +23,13 @@ import {selectOrganizationPrompt} from '../prompts/dev.js'
import {
DEFAULT_CONFIG,
testDeveloperPlatformClient,
testApp,
testAppWithConfig,
testOrganizationApp,
testThemeExtensions,
buildVersionedAppSchema,
} from '../models/app/app.test-data.js'
import metadata from '../metadata.js'
import {AppConfigurationStateLinked, getAppConfigurationFileName, isWebType, loadApp} from '../models/app/loader.js'
import {AppInterface, AppLinkedInterface} from '../models/app/app.js'
import {AppLinkedInterface} from '../models/app/app.js'
import * as loadSpecifications from '../models/extensions/load-specifications.js'
import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../utilities/developer-platform-client.js'
import {RemoteAwareExtensionSpecification} from '../models/extensions/specification.js'
Expand Down Expand Up @@ -221,11 +220,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -270,11 +269,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -320,11 +319,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -370,11 +369,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -426,11 +425,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -470,11 +469,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -511,11 +510,11 @@ describe('ensureDeployContext', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down Expand Up @@ -689,11 +688,72 @@ describe('ensureThemeExtensionDevContext', () => {
})
})

async function mockApp(directory: string, app?: Partial<AppInterface>) {
const versionSchema = await buildVersionedAppSchema()
const localApp = testApp(app)
localApp.configSchema = versionSchema.schema
localApp.specifications = versionSchema.configSpecifications
localApp.directory = directory
return localApp
}
describe('appFromIdentifiers', () => {
test('renders the org name when an app cannot be found and the account is a service account ', async () => {
vi.mocked(isServiceAccount).mockReturnValue(true)

await expect(
appFromIdentifiers({
apiKey: 'apiKey-12345',
developerPlatformClient: testDeveloperPlatformClient({
appFromIdentifiers: () => Promise.resolve(undefined),
accountInfo: () =>
Promise.resolve({
type: 'ServiceAccount',
orgName: 'My Test Org',
}),
}),
organizationId: 'orgId',
}),
).rejects.toThrowError(
expect.objectContaining({
message: 'No app with client ID apiKey-12345 found',
tryMessage: renderTryMessage(true, 'My Test Org'),
}),
)
})

test('renders the user email when an app cannot be found and the account is a user account ', async () => {
vi.mocked(isUserAccount).mockReturnValue(true)

await expect(
appFromIdentifiers({
apiKey: 'apiKey-12345',
developerPlatformClient: testDeveloperPlatformClient({
appFromIdentifiers: () => Promise.resolve(undefined),
accountInfo: () =>
Promise.resolve({
type: 'UserAccount',
email: 'user@example.com',
}),
}),
organizationId: 'orgId',
}),
).rejects.toThrowError(
expect.objectContaining({
message: 'No app with client ID apiKey-12345 found',
tryMessage: renderTryMessage(false, 'user@example.com'),
}),
)
})
})

const renderTryMessage = (isOrg: boolean, identifier: string) => [
{
list: {
title: 'Next steps:',
items: [
'Check that your account has permission to develop apps for this organization or contact the owner of the organization to grant you permission',
[
'Run',
{command: 'shopify auth logout'},
'to log into a different',
isOrg ? 'organization' : 'account',
'than',
{bold: identifier},
],
['Pass', {command: '--reset'}, 'to your command to create a new app'],
],
},
},
]
45 changes: 41 additions & 4 deletions packages/app/src/cli/services/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {createExtension} from './dev/create-extension.js'
import {CachedAppInfo} from './local-storage.js'
import {patchAppConfigurationFile} from './app/patch-app-configuration-file.js'
import {DeployOptions} from './deploy.js'
import {isServiceAccount, isUserAccount} from './context/partner-account-info.js'
import {selectOrganizationPrompt} from '../prompts/dev.js'
import {
AppInterface,
Expand Down Expand Up @@ -38,10 +39,30 @@ export const InvalidApiKeyErrorMessage = (apiKey: string) => {
}
}

export const resetHelpMessage: Token[] = [
'You can pass ',
export const resetHelpMessage = [
'You can pass',
{command: '--reset'},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
]

const appNotFoundHelpMessage = (accountIdentifier: string, isOrg = false) => [
{
list: {
title: 'Next steps:',
items: [
'Check that your account has permission to develop apps for this organization or contact the owner of the organization to grant you permission',
[
'Run',
{command: 'shopify auth logout'},
'to log into a different',
isOrg ? 'organization' : 'account',
'than',
{bold: accountIdentifier},
],
['Pass', {command: '--reset'}, 'to your command to create a new app'],
],
},
},
]

interface AppFromIdOptions {
Expand All @@ -65,7 +86,23 @@ export const appFromIdentifiers = async (options: AppFromIdOptions): Promise<Org
apiKey: options.apiKey,
organizationId,
})
if (!app) throw new AbortError([`Couldn't find the app with Client ID`, {command: options.apiKey}], resetHelpMessage)
if (!app) {
const accountInfo = await developerPlatformClient.accountInfo()
let identifier = 'Unknown account'
let isOrg = false

if (isServiceAccount(accountInfo)) {
identifier = accountInfo.orgName
isOrg = true
} else if (isUserAccount(accountInfo)) {
identifier = accountInfo.email
}

throw new AbortError(
[`No app with client ID`, {command: options.apiKey}, 'found'],
appNotFoundHelpMessage(identifier, isOrg),
)
}
return app
}

Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/cli/services/logs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,11 @@ describe('logs', () => {
},
},
'\n',
'You can pass ',
'You can pass',
{
command: '--reset',
},
' to your command to reset your app configuration.',
'to your command to reset your app configuration.',
],
headline: 'Using shopify.app.toml for default values:',
})
Expand Down

0 comments on commit 14612d3

Please sign in to comment.