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
2 changes: 1 addition & 1 deletion .changeset/store-list-bp-auto.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
'@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.
Add `shopify store list` with `--from auto|organization|store-auth`. By default the command prefers your Shopify organization and falls back to locally stored store auth when the organization can't be listed for the current CLI session. When some organization lookups fail, the command still returns successful organizations with a warning and failure details for the skipped organizations.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
* @publicDocs
*/
export interface storelist {
/**
* Source for the listing. `auto` prefers your Shopify organization and falls back to locally stored store auth when necessary.
* @environment SHOPIFY_FLAG_STORE_LIST_FROM
*/
'--from <value>'?: string

/**
* Output the result as JSON. Automatically disables color output.
* @environment SHOPIFY_FLAG_JSON
Expand All @@ -17,7 +23,7 @@ export interface storelist {
'--no-color'?: ''

/**
* List stores for a single organization, by ID.
* List stores for a single organization, by ID. Only valid with `--from auto` or `--from organization`.
* @environment SHOPIFY_FLAG_ORGANIZATION_ID
*/
'--organization-id <value>'?: string
Expand Down
13 changes: 11 additions & 2 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4300,6 +4300,15 @@
"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": "--from <value>",
"value": "string",
"description": "Source for the listing. `auto` prefers your Shopify organization and falls back to locally stored store auth when necessary.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_LIST_FROM"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-list.interface.ts",
"syntaxKind": "PropertySignature",
Expand All @@ -4314,7 +4323,7 @@
"syntaxKind": "PropertySignature",
"name": "--organization-id <value>",
"value": "string",
"description": "List stores for a single organization, by ID.",
"description": "List stores for a single organization, by ID. Only valid with `--from auto` or `--from organization`.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_ORGANIZATION_ID"
},
Expand All @@ -4337,7 +4346,7 @@
"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 * List stores for a single organization, by ID.\n * @environment SHOPIFY_FLAG_ORGANIZATION_ID\n */\n '--organization-id <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface storelist {\n /**\n * Source for the listing. `auto` prefers your Shopify organization and falls back to locally stored store auth when necessary.\n * @environment SHOPIFY_FLAG_STORE_LIST_FROM\n */\n '--from <value>'?: string\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 * List stores for a single organization, by ID. Only valid with `--from auto` or `--from organization`.\n * @environment SHOPIFY_FLAG_ORGANIZATION_ID\n */\n '--organization-id <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"themecheck": {
Expand Down
15 changes: 15 additions & 0 deletions packages/cli-kit/src/public/node/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,21 @@ describe('ensureAuthenticatedBusinessPlatform', () => {
expect(got).toEqual('business_platform')
})

test('passes auth options through to the private auth helper', async () => {
// Given
vi.mocked(ensureAuthenticated).mockResolvedValueOnce({businessPlatform: 'business_platform', userId: '1234-5678'})

// When
await ensureAuthenticatedBusinessPlatform([], {noPrompt: true})

// Then
expect(ensureAuthenticated).toHaveBeenCalledWith(
{businessPlatformApi: {scopes: []}},
process.env,
expect.objectContaining({noPrompt: true}),
)
})

test('throws error if there is no business_platform token', async () => {
// Given
vi.mocked(ensureAuthenticated).mockResolvedValueOnce({partners: 'partners_token', userId: '1234-5678'})
Expand Down
8 changes: 6 additions & 2 deletions packages/cli-kit/src/public/node/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,17 @@ ${outputToken.json(scopes)}
* Ensure that we have a valid session to access the Business Platform API.
*
* @param scopes - Optional array of extra scopes to authenticate with.
* @param options - Optional auth behavior overrides such as `noPrompt`.
* @returns The access token for the Business Platform API.
*/
export async function ensureAuthenticatedBusinessPlatform(scopes: BusinessPlatformScope[] = []): Promise<string> {
export async function ensureAuthenticatedBusinessPlatform(
scopes: BusinessPlatformScope[] = [],
options: EnsureAuthenticatedAdditionalOptions = {},
): Promise<string> {
outputDebug(outputContent`Ensuring that the user is authenticated with the Business Platform API with the following scopes:
${outputToken.json(scopes)}
`)
const tokens = await ensureAuthenticated({businessPlatformApi: {scopes}}, process.env)
const tokens = await ensureAuthenticated({businessPlatformApi: {scopes}}, process.env, options)
if (!tokens.businessPlatform) {
throw new BugError('No business-platform token found after ensuring authenticated')
}
Expand Down
30 changes: 24 additions & 6 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2182,31 +2182,49 @@ EXAMPLES

## `shopify store list`

List stores in your Shopify organization.
List stores available to the current CLI session.

```
USAGE
$ shopify store list [-j] [--no-color] [--organization-id <value>] [--verbose]
$ shopify store list [--from auto|organization|store-auth] [-j] [--no-color] [--organization-id <value>]
[--verbose]

FLAGS
-j, --json [env: SHOPIFY_FLAG_JSON] Output the result as JSON. Automatically disables color
output.
--from=<option> [default: auto, env: SHOPIFY_FLAG_STORE_LIST_FROM] Source for the listing. `auto`
prefers your Shopify organization and falls back to locally stored store auth when
necessary.
<options: auto|organization|store-auth>
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
--organization-id=<value> [env: SHOPIFY_FLAG_ORGANIZATION_ID] List stores for a single organization, by ID.
--organization-id=<value> [env: SHOPIFY_FLAG_ORGANIZATION_ID] List stores for a single organization, by ID. Only
valid with `--from auto` or `--from organization`.
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.

DESCRIPTION
List stores in your Shopify organization.
List stores available to the current CLI session.

Lists stores for the current Shopify CLI account.

By default, the command uses `--from auto`:
- your Shopify organization when the current CLI session can be used without reauthentication
- the locally stored store auth cache when the organization can't be listed for the current session

Lists the stores in the Shopify organizations available to the current CLI account.
Use `--from organization` to only list organization stores, or `--from store-auth` to inspect only stores connected
with `shopify store auth`.

Use `--organization-id` to list a single organization. Run `shopify organization list` to find organization IDs.
Use `--organization-id` to scope the organization listing to a single organization. Run `shopify organization list` to
find organization IDs.

EXAMPLES
$ shopify store list

$ shopify store list --from organization

$ shopify store list --organization-id 1234567

$ shopify store list --from store-auth

$ shopify store list --json
```

Expand Down
25 changes: 21 additions & 4 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5868,14 +5868,31 @@
"args": {
},
"customPluginName": "@shopify/store",
"description": "Lists the stores in the Shopify organizations available to the current CLI account.\n\nUse `--organization-id` to list a single organization. Run `shopify organization list` to find organization IDs.",
"descriptionWithMarkdown": "Lists the stores in the Shopify organizations available to the current CLI account.\n\nUse `--organization-id` to list a single organization. Run `shopify organization list` to find organization IDs.",
"description": "Lists stores for the current Shopify CLI account.\n\nBy default, the command uses `--from auto`:\n- your Shopify organization when the current CLI session can be used without reauthentication\n- the locally stored store auth cache when the organization can't be listed for the current session\n\nUse `--from organization` to only list organization stores, or `--from store-auth` to inspect only stores connected with `shopify store auth`.\n\nUse `--organization-id` to scope the organization listing to a single organization. Run `shopify organization list` to find organization IDs.",
"descriptionWithMarkdown": "Lists stores for the current Shopify CLI account.\n\nBy default, the command uses `--from auto`:\n- your Shopify organization when the current CLI session can be used without reauthentication\n- the locally stored store auth cache when the organization can't be listed for the current session\n\nUse `--from organization` to only list organization stores, or `--from store-auth` to inspect only stores connected with `shopify store auth`.\n\nUse `--organization-id` to scope the organization listing to a single organization. Run `shopify organization list` to find organization IDs.",
"examples": [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --from organization",
"<%= config.bin %> <%= command.id %> --organization-id 1234567",
"<%= config.bin %> <%= command.id %> --from store-auth",
"<%= config.bin %> <%= command.id %> --json"
],
"flags": {
"from": {
"default": "auto",
"description": "Source for the listing. `auto` prefers your Shopify organization and falls back to locally stored store auth when necessary.",
"env": "SHOPIFY_FLAG_STORE_LIST_FROM",
"hasDynamicHelp": false,
"multiple": false,
"name": "from",
"options": [
"auto",
"organization",
"store-auth"
],
"required": false,
"type": "option"
},
"json": {
"allowNo": false,
"char": "j",
Expand All @@ -5894,7 +5911,7 @@
"type": "boolean"
},
"organization-id": {
"description": "List stores for a single organization, by ID.",
"description": "List stores for a single organization, by ID. Only valid with `--from auto` or `--from organization`.",
"env": "SHOPIFY_FLAG_ORGANIZATION_ID",
"hasDynamicHelp": false,
"multiple": false,
Expand All @@ -5919,7 +5936,7 @@
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "List stores in your Shopify organization."
"summary": "List stores available to the current CLI session."
},
"theme:check": {
"aliases": [
Expand Down
32 changes: 16 additions & 16 deletions packages/store/src/cli/commands/store/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@ vi.mock('../../services/store/list/result.js')
vi.mock('../../services/store/attribution.js')

describe('store list command', () => {
test('runs the list service and writes text output by default', async () => {
vi.mocked(listStores).mockResolvedValue({entries: []})
test('passes the parsed --from flag through to the list service', async () => {
vi.mocked(listStores).mockResolvedValue({entries: [], source: 'store-auth'})

await StoreList.run([])
await StoreList.run(['--from', 'store-auth'])

expect(listStores).toHaveBeenCalledWith({organizationId: undefined})
expect(writeStoreListResult).toHaveBeenCalledWith({entries: []}, 'text')
expect(listStores).toHaveBeenCalledWith({source: 'store-auth'})
expect(writeStoreListResult).toHaveBeenCalledWith({entries: [], source: 'store-auth'}, 'text')
})

test('passes the organization id through to the list service', async () => {
vi.mocked(listStores).mockResolvedValue({entries: []})
test('defaults to the auto source and writes json output when requested', async () => {
vi.mocked(listStores).mockResolvedValue({entries: [], source: 'organization'})

await StoreList.run(['--organization-id', '1234567'])
await StoreList.run(['--json'])

expect(listStores).toHaveBeenCalledWith({organizationId: '1234567'})
expect(listStores).toHaveBeenCalledWith({source: 'auto', organizationId: undefined})
expect(writeStoreListResult).toHaveBeenCalledWith({entries: [], source: 'organization'}, 'json')
})

test('writes json output when requested', async () => {
vi.mocked(listStores).mockResolvedValue({entries: []})
test('passes the organization id through to the list service', async () => {
vi.mocked(listStores).mockResolvedValue({entries: [], source: 'organization'})

await StoreList.run(['--json'])
await StoreList.run(['--organization-id', '1234567'])

expect(listStores).toHaveBeenCalledWith({organizationId: undefined})
expect(writeStoreListResult).toHaveBeenCalledWith({entries: []}, 'json')
expect(listStores).toHaveBeenCalledWith({source: 'auto', organizationId: '1234567'})
})

test('defines the expected flags', () => {
expect(StoreList.flags.json).toBeDefined()
expect(StoreList.flags.from).toBeDefined()
expect(StoreList.flags['organization-id']).toBeDefined()
expect('source' in StoreList.flags).toBe(false)
expect(StoreList.flags.json).toBeDefined()
})
})
33 changes: 28 additions & 5 deletions packages/store/src/cli/commands/store/list.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import {listStores} from '../../services/store/list/index.js'
import {type StoreListRequestedSource} from '../../services/store/list/types.js'
import {writeStoreListResult} from '../../services/store/list/result.js'
import StoreCommand from '../../utilities/store-command.js'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {Flags} from '@oclif/core'

const STORE_LIST_SOURCES: StoreListRequestedSource[] = ['auto', 'organization', 'store-auth']

export default class StoreList extends StoreCommand {
static summary = 'List stores in your Shopify organization.'
static summary = 'List stores available to the current CLI session.'

static descriptionWithMarkdown = `Lists stores for the current Shopify CLI account.

By default, the command uses \
\`--from auto\`:\n- your Shopify organization when the current CLI session can be used without reauthentication\n- the locally stored store auth cache when the organization can't be listed for the current session

static descriptionWithMarkdown = `Lists the stores in the Shopify organizations available to the current CLI account.
Use \
\`--from organization\` to only list organization stores, or \
\`--from store-auth\` to inspect only stores connected with \`shopify store auth\`.

Use \`--organization-id\` to list a single organization. Run \`shopify organization list\` to find organization IDs.`
Use \`--organization-id\` to scope the organization listing to a single organization. Run \`shopify organization list\` to find organization IDs.`

static description = this.descriptionWithoutMarkdown()

static examples = [
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> --from organization',
'<%= config.bin %> <%= command.id %> --organization-id 1234567',
'<%= config.bin %> <%= command.id %> --from store-auth',
'<%= config.bin %> <%= command.id %> --json',
]

static flags = {
...globalFlags,
...jsonFlag,
from: Flags.string({
description:
'Source for the listing. `auto` prefers your Shopify organization and falls back to locally stored store auth when necessary.',
env: 'SHOPIFY_FLAG_STORE_LIST_FROM',
options: STORE_LIST_SOURCES,
default: 'auto',
required: false,
}),
'organization-id': Flags.string({
description: 'List stores for a single organization, by ID.',
description: 'List stores for a single organization, by ID. Only valid with `--from auto` or `--from organization`.',
env: 'SHOPIFY_FLAG_ORGANIZATION_ID',
required: false,
}),
}

public async run(): Promise<void> {
const {flags} = await this.parse(StoreList)
const result = await listStores({organizationId: flags['organization-id']})
const result = await listStores({
source: flags.from as StoreListRequestedSource,
organizationId: flags['organization-id'],
})

writeStoreListResult(result, flags.json ? 'json' : 'text')
}
Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/cli/services/store/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/out
import {AbortError} from '@shopify/cli-kit/node/error'
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'

export {listStoredStoreAuthSummaries, type StoredStoreAuthSummary} from './stored-auth.js'
export {listStoredStoreAuthSummaries} from './stored-auth.js'

interface StoreAuthInput {
store: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {listCurrentStoredStoreAppSessions, type StoredStoreAppSession} from './session-store.js'

export interface StoredStoreAuthSummary {
interface StoredStoreAuthSummary {
store: string
userId: string
scopes: string[]
Expand Down
Loading
Loading