Skip to content

Commit 271d8f9

Browse files
feat(store): open GraphiQL when 'shopify store execute' has no query
When neither --query nor --query-file is provided, 'shopify store execute' now spins up the GraphiQL HTTP server on a local port (using the TokenProvider-based server from @shopify/cli-kit/node/graphiql/server), prints the URL, and opens it in the browser. - New service: openStoreGraphiQL backs the no-query branch. Token strategy delegates to loadStoredStoreSession, so the GraphiQL session uses the credentials previously set up by 'shopify store auth' and benefits from its expiry/refresh logic. - New flags: --graphiql-port (env SHOPIFY_FLAG_GRAPHIQL_PORT) chooses the port; --no-open (env SHOPIFY_FLAG_NO_OPEN) keeps the browser closed. - --query and --query-file are now optional and mutually exclusive instead of exactlyOne. Existing non-interactive behavior is unchanged. - --allow-mutations also gates mutations in the interactive UI, mirroring the safe-by-default semantics of the non-interactive path. Without it, the proxy returns HTTP 400 for any mutation operation. - --output-file and --json log a warning and are ignored in GraphiQL mode. - Tests cover the routing, flag forwarding, and the openStoreGraphiQL service (server wiring, token provider, browser open, abort/close). - README + oclif manifest regenerated; changeset added.
1 parent 5297f10 commit 271d8f9

7 files changed

Lines changed: 402 additions & 19 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@shopify/cli-kit': minor
3+
'@shopify/app': minor
4+
'@shopify/store': minor
5+
---
6+
7+
Open an interactive GraphiQL UI when running `shopify store execute` without `--query` or `--query-file`. The GraphiQL session uses the access token previously set up via `shopify store auth`, points at the same Admin GraphQL endpoint as the non-interactive mode, and respects `--allow-mutations` (mutations are blocked by default). The GraphiQL HTTP server has moved into `@shopify/cli-kit/node/graphiql/server` so both `shopify app dev` and `shopify store execute` can reuse it; behavior of `shopify app dev` is unchanged.

packages/cli/README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,41 +2141,50 @@ EXAMPLES
21412141

21422142
## `shopify store execute`
21432143

2144-
Execute GraphQL queries and mutations on a store.
2144+
Execute GraphQL queries and mutations on a store, or open an interactive GraphiQL UI.
21452145

21462146
```
21472147
USAGE
2148-
$ shopify store execute -s <value> [--allow-mutations] [-j] [--no-color] [--output-file <value>] [-q <value>]
2149-
[--query-file <value>] [--variable-file <value> | -v <value>] [--verbose] [--version <value>]
2148+
$ shopify store execute -s <value> [--allow-mutations] [--graphiql-port <value>] [-j] [--no-color] [--no-open]
2149+
[--output-file <value>] [-q <value> | --query-file <value>] [--variable-file <value> | -v <value>] [--verbose]
2150+
[--version <value>]
21502151
21512152
FLAGS
21522153
-j, --json [env: SHOPIFY_FLAG_JSON] Output the result as JSON. Automatically disables color output.
2153-
-q, --query=<value> [env: SHOPIFY_FLAG_QUERY] The GraphQL query or mutation, as a string.
2154+
-q, --query=<value> [env: SHOPIFY_FLAG_QUERY] The GraphQL query or mutation, as a string. Omit to open
2155+
GraphiQL.
21542156
-s, --store=<value> (required) [env: SHOPIFY_FLAG_STORE] The myshopify.com domain of the store to execute
21552157
against.
21562158
-v, --variables=<value> [env: SHOPIFY_FLAG_VARIABLES] The values for any GraphQL variables in your query or
21572159
mutation, in JSON format.
21582160
--allow-mutations [env: SHOPIFY_FLAG_ALLOW_MUTATIONS] Allow GraphQL mutations to run against the target
21592161
store.
2162+
--graphiql-port=<value> [env: SHOPIFY_FLAG_GRAPHIQL_PORT] Local port for the GraphiQL server when no --query or
2163+
--query-file is provided.
21602164
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
2165+
--no-open [env: SHOPIFY_FLAG_NO_OPEN] Do not open the GraphiQL URL in the browser automatically.
21612166
--output-file=<value> [env: SHOPIFY_FLAG_OUTPUT_FILE] The file name where results should be written, instead of
21622167
STDOUT.
21632168
--query-file=<value> [env: SHOPIFY_FLAG_QUERY_FILE] Path to a file containing the GraphQL query or mutation.
2164-
Can't be used with --query.
2169+
Can't be used with --query. Omit to open GraphiQL.
21652170
--variable-file=<value> [env: SHOPIFY_FLAG_VARIABLE_FILE] Path to a file containing GraphQL variables in JSON
21662171
format. Can't be used with --variables.
21672172
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.
21682173
--version=<value> [env: SHOPIFY_FLAG_VERSION] The API version to use for the query or mutation. Defaults to
21692174
the latest stable version.
21702175
21712176
DESCRIPTION
2172-
Execute GraphQL queries and mutations on a store.
2177+
Execute GraphQL queries and mutations on a store, or open an interactive GraphiQL UI.
21732178
21742179
Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.
21752180
21762181
Run `shopify store auth` first to create stored auth for the store.
21772182
2178-
Mutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data.
2183+
When neither `--query` nor `--query-file` is provided, opens a local GraphiQL UI in the browser pointed at the store.
2184+
Use `--graphiql-port` to choose the port and `--no-open` to keep the browser closed.
2185+
2186+
Mutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data; the same flag
2187+
controls whether the GraphiQL UI is allowed to issue mutations.
21792188
21802189
EXAMPLES
21812190
$ shopify store execute --store shop.myshopify.com --query "query { shop { name } }"
@@ -2185,6 +2194,8 @@ EXAMPLES
21852194
$ shopify store execute --store shop.myshopify.com --query "mutation { shop { id } }" --allow-mutations
21862195
21872196
$ shopify store execute --store shop.myshopify.com --query "query { shop { name } }" --json
2197+
2198+
$ shopify store execute --store shop.myshopify.com
21882199
```
21892200

21902201
## `shopify theme check`

packages/cli/oclif.manifest.json

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5868,13 +5868,14 @@
58685868
"args": {
58695869
},
58705870
"customPluginName": "@shopify/store",
5871-
"description": "Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.\n\nRun `shopify store auth` first to create stored auth for the store.\n\nMutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data.",
5872-
"descriptionWithMarkdown": "Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.\n\nRun `shopify store auth` first to create stored auth for the store.\n\nMutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data.",
5871+
"description": "Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.\n\nRun `shopify store auth` first to create stored auth for the store.\n\nWhen neither `--query` nor `--query-file` is provided, opens a local GraphiQL UI in the browser pointed at the store. Use `--graphiql-port` to choose the port and `--no-open` to keep the browser closed.\n\nMutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data; the same flag controls whether the GraphiQL UI is allowed to issue mutations.",
5872+
"descriptionWithMarkdown": "Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.\n\nRun `shopify store auth` first to create stored auth for the store.\n\nWhen neither `--query` nor `--query-file` is provided, opens a local GraphiQL UI in the browser pointed at the store. Use `--graphiql-port` to choose the port and `--no-open` to keep the browser closed.\n\nMutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data; the same flag controls whether the GraphiQL UI is allowed to issue mutations.",
58735873
"examples": [
58745874
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query \"query { shop { name } }\"",
58755875
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query-file ./operation.graphql --variables '{\"id\":\"gid://shopify/Product/1\"}'",
58765876
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query \"mutation { shop { id } }\" --allow-mutations",
5877-
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query \"query { shop { name } }\" --json"
5877+
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query \"query { shop { name } }\" --json",
5878+
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com"
58785879
],
58795880
"flags": {
58805881
"allow-mutations": {
@@ -5884,6 +5885,14 @@
58845885
"name": "allow-mutations",
58855886
"type": "boolean"
58865887
},
5888+
"graphiql-port": {
5889+
"description": "Local port for the GraphiQL server when no --query or --query-file is provided.",
5890+
"env": "SHOPIFY_FLAG_GRAPHIQL_PORT",
5891+
"hasDynamicHelp": false,
5892+
"multiple": false,
5893+
"name": "graphiql-port",
5894+
"type": "option"
5895+
},
58875896
"json": {
58885897
"allowNo": false,
58895898
"char": "j",
@@ -5901,6 +5910,13 @@
59015910
"name": "no-color",
59025911
"type": "boolean"
59035912
},
5913+
"no-open": {
5914+
"allowNo": false,
5915+
"description": "Do not open the GraphiQL URL in the browser automatically.",
5916+
"env": "SHOPIFY_FLAG_NO_OPEN",
5917+
"name": "no-open",
5918+
"type": "boolean"
5919+
},
59045920
"output-file": {
59055921
"description": "The file name where results should be written, instead of STDOUT.",
59065922
"env": "SHOPIFY_FLAG_OUTPUT_FILE",
@@ -5911,17 +5927,23 @@
59115927
},
59125928
"query": {
59135929
"char": "q",
5914-
"description": "The GraphQL query or mutation, as a string.",
5930+
"description": "The GraphQL query or mutation, as a string. Omit to open GraphiQL.",
59155931
"env": "SHOPIFY_FLAG_QUERY",
5932+
"exclusive": [
5933+
"query-file"
5934+
],
59165935
"hasDynamicHelp": false,
59175936
"multiple": false,
59185937
"name": "query",
59195938
"required": false,
59205939
"type": "option"
59215940
},
59225941
"query-file": {
5923-
"description": "Path to a file containing the GraphQL query or mutation. Can't be used with --query.",
5942+
"description": "Path to a file containing the GraphQL query or mutation. Can't be used with --query. Omit to open GraphiQL.",
59245943
"env": "SHOPIFY_FLAG_QUERY_FILE",
5944+
"exclusive": [
5945+
"query"
5946+
],
59255947
"hasDynamicHelp": false,
59265948
"multiple": false,
59275949
"name": "query-file",
@@ -5985,7 +6007,7 @@
59856007
"pluginName": "@shopify/cli",
59866008
"pluginType": "core",
59876009
"strict": true,
5988-
"summary": "Execute GraphQL queries and mutations on a store."
6010+
"summary": "Execute GraphQL queries and mutations on a store, or open an interactive GraphiQL UI."
59896011
},
59906012
"theme:check": {
59916013
"aliases": [

packages/store/src/cli/commands/store/execute.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import StoreExecute from './execute.js'
22
import {executeStoreOperation} from '../../services/store/execute/index.js'
3+
import {openStoreGraphiQL} from '../../services/store/execute/graphiql.js'
34
import {writeOrOutputStoreExecuteResult} from '../../services/store/execute/result.js'
45
import {beforeEach, describe, expect, test, vi} from 'vitest'
56

67
vi.mock('../../services/store/execute/index.js')
78
vi.mock('../../services/store/execute/result.js')
9+
vi.mock('../../services/store/execute/graphiql.js')
810

911
describe('store execute command', () => {
1012
beforeEach(() => {
1113
vi.mocked(executeStoreOperation).mockResolvedValue({data: {shop: {name: 'Test shop'}}})
14+
vi.mocked(openStoreGraphiQL).mockResolvedValue()
1215
})
1316

1417
test('passes the inline query through to the service and writes the result', async () => {
@@ -52,5 +55,64 @@ describe('store execute command', () => {
5255
expect(StoreExecute.flags['variable-file']).toBeDefined()
5356
expect(StoreExecute.flags['allow-mutations']).toBeDefined()
5457
expect(StoreExecute.flags.json).toBeDefined()
58+
expect(StoreExecute.flags['graphiql-port']).toBeDefined()
59+
expect(StoreExecute.flags['no-open']).toBeDefined()
60+
})
61+
62+
describe('GraphiQL mode (no --query / --query-file)', () => {
63+
test('opens GraphiQL with --allow-mutations false by default', async () => {
64+
await StoreExecute.run(['--store', 'shop.myshopify.com'])
65+
66+
expect(openStoreGraphiQL).toHaveBeenCalledWith({
67+
store: 'shop.myshopify.com',
68+
port: undefined,
69+
open: true,
70+
allowMutations: false,
71+
query: undefined,
72+
variables: undefined,
73+
apiVersion: undefined,
74+
})
75+
expect(executeStoreOperation).not.toHaveBeenCalled()
76+
expect(writeOrOutputStoreExecuteResult).not.toHaveBeenCalled()
77+
})
78+
79+
test('forwards --variables and --version as prefilled values for GraphiQL', async () => {
80+
await StoreExecute.run([
81+
'--store',
82+
'shop.myshopify.com',
83+
'--variables',
84+
'{"id":"gid://shopify/Product/1"}',
85+
'--version',
86+
'2024-10',
87+
])
88+
89+
expect(openStoreGraphiQL).toHaveBeenCalledWith(
90+
expect.objectContaining({
91+
variables: '{"id":"gid://shopify/Product/1"}',
92+
apiVersion: '2024-10',
93+
}),
94+
)
95+
})
96+
97+
test('respects --graphiql-port and --no-open', async () => {
98+
await StoreExecute.run(['--store', 'shop.myshopify.com', '--graphiql-port', '9123', '--no-open'])
99+
100+
expect(openStoreGraphiQL).toHaveBeenCalledWith(
101+
expect.objectContaining({
102+
port: 9123,
103+
open: false,
104+
}),
105+
)
106+
})
107+
108+
test('forwards --allow-mutations to the GraphiQL session', async () => {
109+
await StoreExecute.run(['--store', 'shop.myshopify.com', '--allow-mutations'])
110+
111+
expect(openStoreGraphiQL).toHaveBeenCalledWith(
112+
expect.objectContaining({
113+
allowMutations: true,
114+
}),
115+
)
116+
})
55117
})
56118
})

packages/store/src/cli/commands/store/execute.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import {executeStoreOperation} from '../../services/store/execute/index.js'
2+
import {openStoreGraphiQL} from '../../services/store/execute/graphiql.js'
23
import {writeOrOutputStoreExecuteResult} from '../../services/store/execute/result.js'
34
import StoreCommand from '../../utilities/store-command.js'
45
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
56
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
7+
import {outputWarn} from '@shopify/cli-kit/node/output'
68
import {resolvePath} from '@shopify/cli-kit/node/path'
79
import {Flags} from '@oclif/core'
810

911
export default class StoreExecute extends StoreCommand {
10-
static summary = 'Execute GraphQL queries and mutations on a store.'
12+
static summary = 'Execute GraphQL queries and mutations on a store, or open an interactive GraphiQL UI.'
1113

1214
static descriptionWithMarkdown = `Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.
1315
1416
Run \`shopify store auth\` first to create stored auth for the store.
1517
16-
Mutations are disabled by default. Re-run with \`--allow-mutations\` if you intend to modify store data.`
18+
When neither \`--query\` nor \`--query-file\` is provided, opens a local GraphiQL UI in the browser pointed at the store. Use \`--graphiql-port\` to choose the port and \`--no-open\` to keep the browser closed.
19+
20+
Mutations are disabled by default. Re-run with \`--allow-mutations\` if you intend to modify store data; the same flag controls whether the GraphiQL UI is allowed to issue mutations.`
1721

1822
static description = this.descriptionWithoutMarkdown()
1923

@@ -22,23 +26,25 @@ Mutations are disabled by default. Re-run with \`--allow-mutations\` if you inte
2226
`<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query-file ./operation.graphql --variables '{"id":"gid://shopify/Product/1"}'`,
2327
'<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query "mutation { shop { id } }" --allow-mutations',
2428
'<%= config.bin %> <%= command.id %> --store shop.myshopify.com --query "query { shop { name } }" --json',
29+
'<%= config.bin %> <%= command.id %> --store shop.myshopify.com',
2530
]
2631

2732
static flags = {
2833
...globalFlags,
2934
...jsonFlag,
3035
query: Flags.string({
3136
char: 'q',
32-
description: 'The GraphQL query or mutation, as a string.',
37+
description: 'The GraphQL query or mutation, as a string. Omit to open GraphiQL.',
3338
env: 'SHOPIFY_FLAG_QUERY',
3439
required: false,
35-
exactlyOne: ['query', 'query-file'],
40+
exclusive: ['query-file'],
3641
}),
3742
'query-file': Flags.string({
38-
description: "Path to a file containing the GraphQL query or mutation. Can't be used with --query.",
43+
description:
44+
"Path to a file containing the GraphQL query or mutation. Can't be used with --query. Omit to open GraphiQL.",
3945
env: 'SHOPIFY_FLAG_QUERY_FILE',
4046
parse: async (input) => resolvePath(input),
41-
exactlyOne: ['query', 'query-file'],
47+
exclusive: ['query'],
4248
}),
4349
variables: Flags.string({
4450
char: 'v',
@@ -73,11 +79,40 @@ Mutations are disabled by default. Re-run with \`--allow-mutations\` if you inte
7379
env: 'SHOPIFY_FLAG_ALLOW_MUTATIONS',
7480
default: false,
7581
}),
82+
'graphiql-port': Flags.integer({
83+
description: 'Local port for the GraphiQL server when no --query or --query-file is provided.',
84+
env: 'SHOPIFY_FLAG_GRAPHIQL_PORT',
85+
}),
86+
'no-open': Flags.boolean({
87+
description: 'Do not open the GraphiQL URL in the browser automatically.',
88+
env: 'SHOPIFY_FLAG_NO_OPEN',
89+
default: false,
90+
}),
7691
}
7792

7893
public async run(): Promise<void> {
7994
const {flags} = await this.parse(StoreExecute)
8095

96+
if (!flags.query && !flags['query-file']) {
97+
if (flags['output-file']) {
98+
outputWarn('--output-file is ignored when opening GraphiQL.')
99+
}
100+
if (flags.json) {
101+
outputWarn('--json is ignored when opening GraphiQL.')
102+
}
103+
104+
await openStoreGraphiQL({
105+
store: flags.store,
106+
port: flags['graphiql-port'],
107+
open: !flags['no-open'],
108+
allowMutations: flags['allow-mutations'],
109+
query: flags.query,
110+
variables: flags.variables,
111+
apiVersion: flags.version,
112+
})
113+
return
114+
}
115+
81116
const result = await executeStoreOperation({
82117
store: flags.store,
83118
query: flags.query,

0 commit comments

Comments
 (0)