Skip to content
Draft
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
24 changes: 24 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
* [`shopify search [query]`](#shopify-search-query)
* [`shopify store auth`](#shopify-store-auth)
* [`shopify store create dev`](#shopify-store-create-dev)
* [`shopify store delete`](#shopify-store-delete)
* [`shopify store execute`](#shopify-store-execute)
* [`shopify theme check`](#shopify-theme-check)
* [`shopify theme console`](#shopify-theme-console)
Expand Down Expand Up @@ -2162,6 +2163,29 @@ DESCRIPTION
Creates a new app development store in your organization.
```

## `shopify store delete`

Delete a development store.

```
USAGE
$ shopify store delete --store <value> [-j] [--no-color] [--organization <value>] [--verbose]

FLAGS
-j, --json [env: SHOPIFY_FLAG_JSON] Output the result as JSON. Automatically disables color output.
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
--organization=<value> [env: SHOPIFY_FLAG_ORGANIZATION] The organization that owns the store (numeric ID).
Auto-selects if you belong to a single org.
--store=<value> (required) [env: SHOPIFY_FLAG_STORE] The domain of the development store to delete (e.g.
my-store.myshopify.com).
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.

DESCRIPTION
Delete a development store.

Deletes an app development store from your organization.
```

## `shopify store execute`

Execute GraphQL queries and mutations on a store.
Expand Down
69 changes: 69 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5925,6 +5925,75 @@
"strict": true,
"summary": "Create a new development store."
},
"store:delete": {
"aliases": [
],
"args": {
},
"customPluginName": "@shopify/store",
"description": "Deletes an app development store from your organization.",
"descriptionWithMarkdown": "Deletes an app development store from your organization.",
"enableJsonFlag": false,
"flags": {
"json": {
"allowNo": false,
"char": "j",
"description": "Output the result as JSON. Automatically disables color output.",
"env": "SHOPIFY_FLAG_JSON",
"hidden": false,
"name": "json",
"type": "boolean"
},
"no-color": {
"allowNo": false,
"description": "Disable color output.",
"env": "SHOPIFY_FLAG_NO_COLOR",
"hidden": false,
"name": "no-color",
"type": "boolean"
},
"organization": {
"aliases": [
"organization-id"
],
"description": "The organization that owns the store (numeric ID). Auto-selects if you belong to a single org.",
"env": "SHOPIFY_FLAG_ORGANIZATION",
"hasDynamicHelp": false,
"multiple": false,
"name": "organization",
"type": "option"
},
"store": {
"aliases": [
"name"
],
"description": "The domain of the development store to delete (e.g. my-store.myshopify.com).",
"env": "SHOPIFY_FLAG_STORE",
"hasDynamicHelp": false,
"multiple": false,
"name": "store",
"required": true,
"type": "option"
},
"verbose": {
"allowNo": false,
"description": "Increase the verbosity of the output.",
"env": "SHOPIFY_FLAG_VERBOSE",
"hidden": false,
"name": "verbose",
"type": "boolean"
}
},
"hasDynamicHelp": false,
"hiddenAliases": [
],
"id": "store:delete",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "Delete a development store."
},
"store:execute": {
"aliases": [
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import * as Types from './types.js'

import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'

export type DeleteAppDevelopmentStoreMutationVariables = Types.Exact<{
storeFqdn: Types.Scalars['String']['input']
}>

export type DeleteAppDevelopmentStoreMutation = {
deleteAppDevelopmentStore?: {
success: boolean
userErrors: {code?: string | null; field: string[]; message: string}[]
} | null
}

export const DeleteAppDevelopmentStore = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: {kind: 'Name', value: 'DeleteAppDevelopmentStore'},
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'storeFqdn'}},
type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: {kind: 'Name', value: 'deleteAppDevelopmentStore'},
arguments: [
{
kind: 'Argument',
name: {kind: 'Name', value: 'storeFqdn'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'storeFqdn'}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'success'}},
{
kind: 'Field',
name: {kind: 'Name', value: 'userErrors'},
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'code'}},
{kind: 'Field', name: {kind: 'Name', value: 'field'}},
{kind: 'Field', name: {kind: 'Name', value: 'message'}},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
],
},
},
],
} as unknown as DocumentNode<DeleteAppDevelopmentStoreMutation, DeleteAppDevelopmentStoreMutationVariables>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mutation DeleteAppDevelopmentStore($storeFqdn: String!) {
deleteAppDevelopmentStore(storeFqdn: $storeFqdn) {
success
userErrors {
code
field
message
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2403,6 +2403,18 @@ type CreateAppDevelopmentStoreResult {
userErrors: [UserError!]
}

type DeleteAppDevelopmentStorePayload {
"""
Whether the store deletion was successfully enqueued.
"""
success: Boolean!

"""
The collection of errors.
"""
userErrors: [UserError!]!
}

input CreateCliTokenOrganizationUserInput {
"""
Organization-wide access conditions to apply to the user.
Expand Down Expand Up @@ -10429,6 +10441,16 @@ type Mutation {
"""
convertUsersToSaml(convertUsersToSamlInput: ConvertUsersToSamlInput!): ConvertUsersToSamlResult!

"""
Delete an App Development Store.
"""
deleteAppDevelopmentStore(
"""
The fully-qualified domain name of the store to delete.
"""
storeFqdn: String!
): DeleteAppDevelopmentStorePayload

"""
Create an App Development Store.
"""
Expand Down
90 changes: 90 additions & 0 deletions packages/store/src/cli/commands/store/delete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import StoreDelete from './delete.js'
import {deleteDevStore} from '../../services/store/delete/dev.js'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputResult} from '@shopify/cli-kit/node/output'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/delete/dev.js')

vi.mock('@shopify/cli-kit/node/output', async (importOriginal) => {
const actual: Record<string, unknown> = await importOriginal()
return {
...actual,
outputResult: vi.fn(),
}
})

describe('store delete command', () => {
test('passes parsed flags through to the service', async () => {
await StoreDelete.run(['--store', 'my-store.myshopify.com'])

expect(deleteDevStore).toHaveBeenCalledWith({
store: 'my-store.myshopify.com',
organization: undefined,
json: false,
})
})

test('passes organization flag through to the service', async () => {
await StoreDelete.run(['--store', 'my-store.myshopify.com', '--organization', '12345'])

expect(deleteDevStore).toHaveBeenCalledWith({
store: 'my-store.myshopify.com',
organization: '12345',
json: false,
})
})

test('passes json flag through to the service', async () => {
await StoreDelete.run(['--store', 'my-store.myshopify.com', '--json'])

expect(deleteDevStore).toHaveBeenCalledWith({
store: 'my-store.myshopify.com',
organization: undefined,
json: true,
})
})

test('defines the expected flags', () => {
expect(StoreDelete.flags.store).toBeDefined()
expect(StoreDelete.flags.organization).toBeDefined()
expect(StoreDelete.flags.json).toBeDefined()
})

test('outputs structured JSON error when --json is active and service throws AbortError', async () => {
vi.mocked(deleteDevStore).mockRejectedValueOnce(new AbortError('Something went wrong'))
const mockExit = vi.spyOn(process, 'exit').mockImplementation((() => {
throw new Error('process.exit')
}) as never)

await expect(StoreDelete.run(['--store', 'my-store.myshopify.com', '--json'])).rejects.toThrow(
'process.exit',
)

const call = vi.mocked(outputResult).mock.calls[0]![0] as string
const parsed = JSON.parse(call)
expect(parsed).toEqual({
error: true,
message: 'Something went wrong',
nextSteps: [],
exitCode: 1,
})
expect(mockExit).toHaveBeenCalledWith(1)

mockExit.mockRestore()
})

test('does not output JSON for non-AbortError even when --json is active', async () => {
vi.mocked(deleteDevStore).mockRejectedValueOnce(new Error('unexpected'))

await expect(StoreDelete.run(['--store', 'my-store.myshopify.com', '--json'])).rejects.toThrow()
expect(vi.mocked(outputResult)).not.toHaveBeenCalled()
})

test('does not output JSON for AbortError when --json is not active', async () => {
vi.mocked(deleteDevStore).mockRejectedValueOnce(new AbortError('Something went wrong'))

await expect(StoreDelete.run(['--store', 'my-store.myshopify.com'])).rejects.toThrow()
expect(vi.mocked(outputResult)).not.toHaveBeenCalled()
})
})
59 changes: 59 additions & 0 deletions packages/store/src/cli/commands/store/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {deleteDevStore} from '../../services/store/delete/dev.js'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputResult} from '@shopify/cli-kit/node/output'
import {Flags} from '@oclif/core'

export default class StoreDelete extends Command {
static summary = 'Delete a development store.'

static descriptionWithMarkdown = 'Deletes an app development store from your organization.'

static description = this.descriptionWithoutMarkdown()

static flags = {
...globalFlags,
...jsonFlag,
store: Flags.string({
description: 'The domain of the development store to delete (e.g. my-store.myshopify.com).',
required: true,
aliases: ['name'],
env: 'SHOPIFY_FLAG_STORE',
}),
organization: Flags.string({
description:
'The organization that owns the store (numeric ID). Auto-selects if you belong to a single org.',
aliases: ['organization-id'],
env: 'SHOPIFY_FLAG_ORGANIZATION',
}),
}

async run(): Promise<void> {
const {flags} = await this.parse(StoreDelete)
try {
await deleteDevStore({
store: flags.store,
organization: flags.organization,
json: flags.json,
})
} catch (error) {
if (flags.json && error instanceof AbortError) {
outputResult(
JSON.stringify(
{
error: true,
message: error.message,
nextSteps: error.nextSteps ?? [],
exitCode: 1,
},
null,
2,
),
)
process.exit(1)
}
throw error
}
}
}
Loading
Loading