Skip to content

Commit fd41a9b

Browse files
committed
Make parameters more explicit, add tests
1 parent 6bd47dd commit fd41a9b

2 files changed

Lines changed: 77 additions & 17 deletions

File tree

packages/app/src/cli/commands/app/init.test.ts

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ import initPrompt from '../../prompts/init/init.js'
33
import initService from '../../services/init/init.js'
44
import {selectDeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
55
import {selectOrg} from '../../services/context.js'
6-
import {appNamePrompt, createAsNewAppPrompt} from '../../prompts/dev.js'
6+
import {fetchOrgFromId, NoOrgError} from '../../services/dev/fetch.js'
7+
import {appNamePrompt, createAsNewAppPrompt, selectAppPrompt} from '../../prompts/dev.js'
78
import {validateFlavorValue, validateTemplateValue} from '../../services/init/validate.js'
8-
import {testAppLinked, testDeveloperPlatformClient, testOrganization} from '../../models/app/app.test-data.js'
9+
import {
10+
testAppLinked,
11+
testDeveloperPlatformClient,
12+
testOrganization,
13+
testOrganizationApp,
14+
} from '../../models/app/app.test-data.js'
915
import {describe, expect, test, vi} from 'vitest'
1016
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
1117
import {generateRandomNameForSubdirectory} from '@shopify/cli-kit/node/fs'
@@ -15,6 +21,13 @@ vi.mock('../../prompts/init/init.js')
1521
vi.mock('../../services/init/init.js')
1622
vi.mock('../../utilities/developer-platform-client.js')
1723
vi.mock('../../services/context.js')
24+
vi.mock('../../services/dev/fetch.js', async (importOriginal) => {
25+
const actual = await importOriginal<typeof import('../../services/dev/fetch.js')>()
26+
return {
27+
...actual,
28+
fetchOrgFromId: vi.fn(),
29+
}
30+
})
1831
vi.mock('../../prompts/dev.js')
1932
vi.mock('../../services/init/validate.js')
2033
vi.mock('@shopify/cli-kit/node/fs')
@@ -75,8 +88,8 @@ describe('Init command', () => {
7588
vi.mocked(inferPackageManager).mockReturnValue('npm')
7689
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
7790

78-
// Mock orgFromId to return the organization
79-
vi.mocked(mockDeveloperPlatformClient.orgFromId).mockResolvedValue(mockOrganization)
91+
// Mock fetchOrgFromId to return the organization
92+
vi.mocked(fetchOrgFromId).mockResolvedValue(mockOrganization)
8093

8194
// Mock the orgAndApps method on the developer platform client
8295
vi.mocked(mockDeveloperPlatformClient.orgAndApps).mockResolvedValue({
@@ -114,7 +127,6 @@ describe('Init command', () => {
114127

115128
test('fails with clear error message when invalid organization-id is provided', async () => {
116129
// Given
117-
const validOrg = testOrganization()
118130
const mockDeveloperPlatformClient = testDeveloperPlatformClient()
119131

120132
// Suppress stderr output for this error test
@@ -127,8 +139,10 @@ describe('Init command', () => {
127139
vi.mocked(inferPackageManager).mockReturnValue('npm')
128140
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
129141

130-
// Mock orgFromId to return undefined for invalid organization
131-
vi.mocked(mockDeveloperPlatformClient.orgFromId).mockResolvedValue(undefined)
142+
// Mock fetchOrgFromId to throw NoOrgError for invalid organization
143+
vi.mocked(fetchOrgFromId).mockRejectedValue(
144+
new NoOrgError({type: 'UserAccount', email: 'test@example.com'}, 'invalid-org-id'),
145+
)
132146

133147
vi.mocked(initPrompt).mockResolvedValue({
134148
template: 'https://github.com/Shopify/shopify-app-template-remix',
@@ -144,7 +158,7 @@ describe('Init command', () => {
144158
).rejects.toThrow('process.exit unexpectedly called with "1"')
145159

146160
// Verify the error message was displayed
147-
expect(outputMock.error()).toContain('Organization with ID invalid-org-id not found')
161+
expect(outputMock.error()).toContain('No Organization found')
148162

149163
// Verify initService was never called since validation failed
150164
expect(initService).not.toHaveBeenCalled()
@@ -153,4 +167,54 @@ describe('Init command', () => {
153167
consoleErrorSpy.mockRestore()
154168
}
155169
})
170+
171+
test('skips app selection prompts when organization has existing apps but --name flag is provided', async () => {
172+
// Given
173+
const mockOrganization = testOrganization()
174+
const mockDeveloperPlatformClient = testDeveloperPlatformClient()
175+
const mockApp = testAppLinked()
176+
const existingApp = testOrganizationApp()
177+
178+
mockAndCaptureOutput()
179+
vi.mocked(validateTemplateValue).mockReturnValue(undefined)
180+
vi.mocked(validateFlavorValue).mockReturnValue(undefined)
181+
vi.mocked(inferPackageManager).mockReturnValue('npm')
182+
vi.mocked(selectDeveloperPlatformClient).mockReturnValue(mockDeveloperPlatformClient)
183+
184+
// Mock fetchOrgFromId to return the organization
185+
vi.mocked(fetchOrgFromId).mockResolvedValue(mockOrganization)
186+
187+
// Mock the orgAndApps method to return existing apps
188+
vi.mocked(mockDeveloperPlatformClient.orgAndApps).mockResolvedValue({
189+
organization: mockOrganization,
190+
apps: [existingApp],
191+
hasMorePages: false,
192+
})
193+
194+
vi.mocked(initPrompt).mockResolvedValue({
195+
template: 'https://github.com/Shopify/shopify-app-template-remix',
196+
templateType: 'remix',
197+
globalCLIResult: {install: false, alreadyInstalled: false},
198+
})
199+
vi.mocked(initService).mockResolvedValue({app: mockApp})
200+
201+
// When
202+
await Init.run(['--organization-id', mockOrganization.id, '--name', 'my-new-app', '--template', 'remix'])
203+
204+
// Then
205+
// Verify that app selection prompts were NOT called even though org has existing apps
206+
expect(selectOrg).not.toHaveBeenCalled()
207+
expect(createAsNewAppPrompt).not.toHaveBeenCalled()
208+
expect(selectAppPrompt).not.toHaveBeenCalled()
209+
expect(appNamePrompt).not.toHaveBeenCalled()
210+
211+
// Verify the command completed successfully with the provided name
212+
expect(initService).toHaveBeenCalledWith(
213+
expect.objectContaining({
214+
name: 'my-new-app',
215+
packageManager: 'npm',
216+
template: 'https://github.com/Shopify/shopify-app-template-remix',
217+
}),
218+
)
219+
})
156220
})

packages/app/src/cli/commands/app/init.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import initPrompt, {visibleTemplates} from '../../prompts/init/init.js'
22
import initService from '../../services/init/init.js'
33
import {DeveloperPlatformClient, selectDeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
44
import {appFromIdentifiers, selectOrg} from '../../services/context.js'
5+
import {fetchOrgFromId} from '../../services/dev/fetch.js'
56
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
67
import {validateFlavorValue, validateTemplateValue} from '../../services/init/validate.js'
78
import {MinimalOrganizationApp, Organization, OrganizationApp} from '../../models/organization.js'
@@ -27,6 +28,8 @@ export default class Init extends AppLinkedCommand {
2728
char: 'n',
2829
env: 'SHOPIFY_FLAG_NAME',
2930
hidden: false,
31+
description:
32+
'The name for the new app. When provided, skips the app selection prompt and creates a new app with this name.',
3033
}),
3134
path: Flags.string({
3235
char: 'p',
@@ -69,6 +72,7 @@ export default class Init extends AppLinkedCommand {
6972
description:
7073
'The organization ID. Your organization ID can be found in your Dev Dashboard URL: https://dev.shopify.com/dashboard/<organization-id>',
7174
env: 'SHOPIFY_FLAG_ORGANIZATION_ID',
75+
exclusive: ['client-id'],
7276
}),
7377
}
7478

@@ -102,15 +106,7 @@ export default class Init extends AppLinkedCommand {
102106
let org: Organization
103107
if (flags['organization-id']) {
104108
// If an organization-id is provided, fetch the organization directly
105-
const matchingOrg = await developerPlatformClient.orgFromId(flags['organization-id'])
106-
if (!matchingOrg) {
107-
throw new AbortError(
108-
`Organization with ID ${flags['organization-id']} not found`,
109-
"Run `shopify auth login` to confirm you've selected the right account, and verify your organization ID. " +
110-
'You can find your organization ID in your Dev Dashboard URL: https://dev.shopify.com/dashboard/<organization-id>',
111-
)
112-
}
113-
org = matchingOrg
109+
org = await fetchOrgFromId(flags['organization-id'], developerPlatformClient)
114110
} else {
115111
org = await selectOrg()
116112
}

0 commit comments

Comments
 (0)