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
8 changes: 8 additions & 0 deletions .changeset/store-list-bp-auto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@shopify/cli': minor
'@shopify/store': minor
'@shopify/organizations': minor
'@shopify/cli-kit': patch
---

Add `shopify store list` to list stores from Business Platform for the current Shopify CLI account. When some organization-scoped Business Platform lookups fail, the command now returns successful organizations with a warning and failure details for the skipped organizations.
5 changes: 4 additions & 1 deletion bin/get-graphql-schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ const schemas = [
owner: 'shop',
repo: 'world',
pathToFile: 'areas/platforms/organizations/db/graphql/organizations_schema.graphql',
localPaths: ['./packages/app/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql'],
localPaths: [
'./packages/app/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql',
'./packages/store/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql',
],
},
{
owner: 'shop',
Expand Down
24 changes: 24 additions & 0 deletions docs-shopify.dev/commands/interfaces/store-list.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This is an autogenerated file. Don't edit this file manually.
/**
* The following flags are available for the `store list` command:
* @publicDocs
*/
export interface storelist {
/**
* Output the result as JSON. Automatically disables color output.
* @environment SHOPIFY_FLAG_JSON
*/
'-j, --json'?: ''

/**
* Disable color output.
* @environment SHOPIFY_FLAG_NO_COLOR
*/
'--no-color'?: ''

/**
* Increase the verbosity of the output.
* @environment SHOPIFY_FLAG_VERBOSE
*/
'--verbose'?: ''
}
38 changes: 38 additions & 0 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4293,6 +4293,44 @@
"value": "export interface storeexecute {\n /**\n * Allow GraphQL mutations to run against the target store.\n * @environment SHOPIFY_FLAG_ALLOW_MUTATIONS\n */\n '--allow-mutations'?: ''\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The file name where results should be written, instead of STDOUT.\n * @environment SHOPIFY_FLAG_OUTPUT_FILE\n */\n '--output-file <value>'?: string\n\n /**\n * The GraphQL query or mutation, as a string.\n * @environment SHOPIFY_FLAG_QUERY\n */\n '-q, --query <value>'?: string\n\n /**\n * Path to a file containing the GraphQL query or mutation. Can't be used with --query.\n * @environment SHOPIFY_FLAG_QUERY_FILE\n */\n '--query-file <value>'?: string\n\n /**\n * The myshopify.com domain of the store to execute against.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Path to a file containing GraphQL variables in JSON format. Can't be used with --variables.\n * @environment SHOPIFY_FLAG_VARIABLE_FILE\n */\n '--variable-file <value>'?: string\n\n /**\n * The values for any GraphQL variables in your query or mutation, in JSON format.\n * @environment SHOPIFY_FLAG_VARIABLES\n */\n '-v, --variables <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n\n /**\n * The API version to use for the query or mutation. Defaults to the latest stable version.\n * @environment SHOPIFY_FLAG_VERSION\n */\n '--version <value>'?: string\n}"
}
},
"storelist": {
"docs-shopify.dev/commands/interfaces/store-list.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/store-list.interface.ts",
"name": "storelist",
"description": "The following flags are available for the `store list` command:",
"isPublicDocs": true,
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/store-list.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--no-color",
"value": "''",
"description": "Disable color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_NO_COLOR"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-list.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--verbose",
"value": "''",
"description": "Increase the verbosity of the output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-list.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-j, --json",
"value": "''",
"description": "Output the result as JSON. Automatically disables color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON"
}
],
"value": "export interface storelist {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"themecheck": {
"docs-shopify.dev/commands/interfaces/theme-check.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/theme-check.interface.ts",
Expand Down
5 changes: 5 additions & 0 deletions graphql.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,10 @@ export default {
functions: projectFactory('functions', 'functions_cli_schema.graphql', 'app'),
adminAsApp: projectFactory('admin', 'admin_schema.graphql'),
organizationsDestinations: projectFactory('business-platform-destinations', 'destinations_schema.graphql', 'organizations'),
storeBusinessPlatformOrganizations: projectFactory(
'business-platform-organizations',
'organizations_schema.graphql',
'store',
),
},
}
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,19 @@
]
}
},
"packages/store": {
"entry": [
"**/{commands,hooks}/**/*.ts!",
"**/index.ts!"
],
"project": "**/*.ts!",
"ignore": [
"**/graphql/**/generated/*.ts"
],
"ignoreDependencies": [
"@graphql-typed-document-node/core"
]
},
"packages/theme": {
"project": "**/*.ts!",
"entry": [
Expand Down
13 changes: 13 additions & 0 deletions packages/cli-kit/src/private/node/session/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ describe('session store', () => {
// Then
expect(result).toEqual(mockSessions)
})

test('returns undefined and discards malformed JSON session content', async () => {
// Given
vi.mocked(getSessions).mockReturnValue('{not valid json')

// When
const result = await fetch()

// Then
expect(result).toBeUndefined()
expect(removeSessions).toHaveBeenCalled()
expect(removeCurrentSessionId).toHaveBeenCalled()
})
})

describe('remove', () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/cli-kit/src/private/node/session/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ export async function fetch(): Promise<Sessions | undefined> {
if (!content) {
return undefined
}
const contentJson = JSON.parse(content)

let contentJson: unknown
try {
contentJson = JSON.parse(content)
} catch (error) {
if (!(error instanceof SyntaxError)) throw error
await remove()
return undefined
}

const parsedSessions = await SessionsSchema.safeParseAsync(contentJson)
if (parsedSessions.success) {
return parsedSessions.data
Expand Down
25 changes: 25 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 execute`](#shopify-store-execute)
* [`shopify store list`](#shopify-store-list)
* [`shopify theme check`](#shopify-theme-check)
* [`shopify theme console`](#shopify-theme-console)
* [`shopify theme delete`](#shopify-theme-delete)
Expand Down Expand Up @@ -2179,6 +2180,30 @@ EXAMPLES
$ shopify store execute --store shop.myshopify.com --query "query { shop { name } }" --json
```

## `shopify store list`

List stores accessible through Business Platform.

```
USAGE
$ shopify store list [-j] [--no-color] [--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.
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.

DESCRIPTION
List stores accessible through Business Platform.

Lists stores from Business Platform for the current Shopify CLI account.

EXAMPLES
$ shopify store list

$ shopify store list --json
```

## `shopify theme check`

Validate the theme.
Expand Down
49 changes: 49 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5862,6 +5862,55 @@
"strict": true,
"summary": "Execute GraphQL queries and mutations on a store."
},
"store:list": {
"aliases": [
],
"args": {
},
"customPluginName": "@shopify/store",
"description": "Lists stores from Business Platform for the current Shopify CLI account.",
"descriptionWithMarkdown": "Lists stores from Business Platform for the current Shopify CLI account.",
"examples": [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --json"
],
"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"
},
"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:list",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "List stores accessible through Business Platform."
},
"theme:check": {
"aliases": [
],
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e/data/snapshots/commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
├─ search
├─ store
│ ├─ auth
│ └─ execute
│ ├─ execute
│ └─ list
├─ theme
│ ├─ check
│ ├─ console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type ListOrganizationsQueryVariables = Types.Exact<{[key: string]: never}
export type ListOrganizationsQuery = {
currentUserAccount?: {
uuid: string
email: string
organizationsWithAccessToDestination: {nodes: {id: string; name: string}[]}
} | null
}
Expand All @@ -29,6 +30,7 @@ export const ListOrganizations = {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'uuid'}},
{kind: 'Field', name: {kind: 'Name', value: 'email'}},
{
kind: 'Field',
name: {kind: 'Name', value: 'organizationsWithAccessToDestination'},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
query ListOrganizations {
currentUserAccount {
uuid
email
organizationsWithAccessToDestination(destination: APPS_CLI) {
nodes {
id
Expand Down
42 changes: 41 additions & 1 deletion packages/organizations/src/cli/services/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {fetchOrganizations} from './fetch.js'
import {fetchOrganizations, fetchOrganizationsWithAccessInfo} from './fetch.js'
import {describe, expect, test, vi} from 'vitest'
import {businessPlatformRequestDoc} from '@shopify/cli-kit/node/api/business-platform'
import {ensureAuthenticatedBusinessPlatform} from '@shopify/cli-kit/node/session'
Expand All @@ -15,6 +15,7 @@ describe('fetchOrganizations', () => {
vi.mocked(businessPlatformRequestDoc).mockResolvedValue({
currentUserAccount: {
uuid: 'user-uuid',
email: 'merchant@example.com',
organizationsWithAccessToDestination: {
nodes: [
{id: ENCODED_GID_1, name: 'My Org'},
Expand Down Expand Up @@ -47,6 +48,7 @@ describe('fetchOrganizations', () => {
vi.mocked(businessPlatformRequestDoc).mockResolvedValue({
currentUserAccount: {
uuid: 'user-uuid',
email: 'merchant@example.com',
organizationsWithAccessToDestination: {
nodes: [],
},
Expand All @@ -62,6 +64,7 @@ describe('fetchOrganizations', () => {
vi.mocked(businessPlatformRequestDoc).mockResolvedValue({
currentUserAccount: {
uuid: 'user-uuid',
email: 'merchant@example.com',
organizationsWithAccessToDestination: {
nodes: [{id: ENCODED_GID_1, name: 'My Org'}],
},
Expand All @@ -80,3 +83,40 @@ describe('fetchOrganizations', () => {
)
})
})

describe('fetchOrganizationsWithAccessInfo', () => {
test('returns organizations plus current-user metadata when the session resolves to a user', async () => {
vi.mocked(ensureAuthenticatedBusinessPlatform).mockResolvedValue('test-token')
vi.mocked(businessPlatformRequestDoc).mockResolvedValue({
currentUserAccount: {
uuid: 'user-uuid',
email: 'merchant@example.com',
organizationsWithAccessToDestination: {
nodes: [{id: ENCODED_GID_1, name: 'My Org'}],
},
},
})

const result = await fetchOrganizationsWithAccessInfo()

expect(result).toEqual({
organizations: [{id: '1234', businessName: 'My Org'}],
currentUserResolved: true,
currentUserEmail: 'merchant@example.com',
})
})

test('returns unresolved current-user metadata when BP cannot resolve currentUserAccount', async () => {
vi.mocked(ensureAuthenticatedBusinessPlatform).mockResolvedValue('test-token')
vi.mocked(businessPlatformRequestDoc).mockResolvedValue({
currentUserAccount: null,
})

const result = await fetchOrganizationsWithAccessInfo()

expect(result).toEqual({
organizations: [],
currentUserResolved: false,
})
})
})
Loading
Loading