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
6 changes: 6 additions & 0 deletions .changeset/auth-list-store-sessions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/cli': minor
'@shopify/store': minor
---

Add `shopify auth list` to list stores authenticated directly with `shopify store auth`.
24 changes: 24 additions & 0 deletions docs-shopify.dev/commands/interfaces/auth-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 `auth list` command:
* @publicDocs
*/
export interface authlist {
/**
* 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 @@ -2577,6 +2577,44 @@
"value": "export interface appwebhooktrigger {\n /**\n * The URL where the webhook payload should be sent.\n You will need a different address type for each delivery-method:\n · For remote HTTP testing, use a URL that starts with https://\n · For local HTTP testing, use http://localhost:{port}/{url-path}\n · For Google Pub/Sub, use pubsub://{project-id}:{topic-id}\n · For Amazon EventBridge, use an Amazon Resource Name (ARN) starting with arn:aws:events:\n * @environment SHOPIFY_FLAG_ADDRESS\n */\n '--address <value>'?: string\n\n /**\n * The API Version of the webhook topic.\n * @environment SHOPIFY_FLAG_API_VERSION\n */\n '--api-version <value>'?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * Your app's client secret. This secret allows us to return the X-Shopify-Hmac-SHA256 header that lets you validate the origin of the response that you receive.\n * @environment SHOPIFY_FLAG_CLIENT_SECRET\n */\n '--client-secret <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Method chosen to deliver the topic payload. If not passed, it's inferred from the address.\n * @environment SHOPIFY_FLAG_DELIVERY_METHOD\n */\n '--delivery-method <value>'?: string\n\n /**\n * This help. When you run the trigger command the CLI will prompt you for any information that isn't passed using flags.\n * @environment SHOPIFY_FLAG_HELP\n */\n '--help'?: ''\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * The requested webhook topic.\n * @environment SHOPIFY_FLAG_TOPIC\n */\n '--topic <value>'?: string\n}"
}
},
"authlist": {
"docs-shopify.dev/commands/interfaces/auth-list.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/auth-list.interface.ts",
"name": "authlist",
"description": "The following flags are available for the `auth list` command:",
"isPublicDocs": true,
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/auth-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/auth-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/auth-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 authlist {\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}"
}
},
"authlogin": {
"docs-shopify.dev/commands/interfaces/auth-login.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/auth-login.interface.ts",
Expand Down
28 changes: 28 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* [`shopify app release --version <version>`](#shopify-app-release---version-version)
* [`shopify app versions list`](#shopify-app-versions-list)
* [`shopify app webhook trigger`](#shopify-app-webhook-trigger)
* [`shopify auth list`](#shopify-auth-list)
* [`shopify auth login`](#shopify-auth-login)
* [`shopify auth logout`](#shopify-auth-logout)
* [`shopify commands`](#shopify-commands)
Expand Down Expand Up @@ -1048,6 +1049,33 @@ DESCRIPTION
- You can't use this method to validate your API webhook subscriptions.
```

## `shopify auth list`

List stores authenticated directly with store auth.

```
USAGE
$ shopify auth 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 authenticated directly with store auth.

Lists stores authenticated directly on this machine with `shopify store auth`.

Use this command to find stores that can be used with store-authenticated commands such as `shopify store execute`.
To list stores in a Shopify organization, run `shopify store list`.

EXAMPLES
$ shopify auth list

$ shopify auth list --json
```

## `shopify auth login`

Logs you in to your Shopify account.
Expand Down
50 changes: 50 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3048,6 +3048,56 @@
"strict": true,
"summary": "Trigger delivery of a sample webhook topic payload to a designated address."
},
"auth:list": {
"aliases": [
],
"args": {
},
"customPluginName": "@shopify/store",
"description": "Lists stores authenticated directly on this machine with `shopify store auth`.\n\nUse this command to find stores that can be used with store-authenticated commands such as `shopify store execute`.\nTo list stores in a Shopify organization, run `shopify store list`.",
"descriptionWithMarkdown": "Lists stores authenticated directly on this machine with `shopify store auth`.\n\nUse this command to find stores that can be used with store-authenticated commands such as `shopify store execute`.\nTo list stores in a Shopify organization, run `shopify store list`.",
"enableJsonFlag": false,
"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": "auth:list",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "List stores authenticated directly with store auth."
},
"auth:login": {
"aliases": [
],
Expand Down
1 change: 1 addition & 0 deletions packages/e2e/data/snapshots/commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
│ └─ webhook
│ └─ trigger
├─ auth
│ ├─ list
│ ├─ login
│ └─ logout
├─ commands
Expand Down
32 changes: 32 additions & 0 deletions packages/store/src/cli/commands/auth/list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import AuthList from './list.js'
import {listStoreAuthSessions} from '../../services/store/auth/list.js'
import {writeStoreAuthListResult} from '../../services/store/auth/list-result.js'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/auth/list.js')
vi.mock('../../services/store/auth/list-result.js')

describe('auth list command', () => {
test('lists direct store-auth sessions and writes text output by default', async () => {
vi.mocked(listStoreAuthSessions).mockReturnValue({sessions: []})

await AuthList.run([])

expect(listStoreAuthSessions).toHaveBeenCalledWith()
expect(writeStoreAuthListResult).toHaveBeenCalledWith({sessions: []}, 'text')
})

test('writes json output when requested', async () => {
vi.mocked(listStoreAuthSessions).mockReturnValue({sessions: []})

await AuthList.run(['--json'])

expect(writeStoreAuthListResult).toHaveBeenCalledWith({sessions: []}, 'json')
})

test('does not expose organization or source-selection flags', () => {
expect(AuthList.flags.json).toBeDefined()
expect(AuthList.flags).not.toHaveProperty('organization-id')
expect(AuthList.flags).not.toHaveProperty('from')
})
})
29 changes: 29 additions & 0 deletions packages/store/src/cli/commands/auth/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {listStoreAuthSessions} from '../../services/store/auth/list.js'
import {writeStoreAuthListResult} from '../../services/store/auth/list-result.js'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'

export default class AuthList extends Command {
static summary = 'List stores authenticated directly with store auth.'

static descriptionWithMarkdown = `Lists stores authenticated directly on this machine with \`shopify store auth\`.

Use this command to find stores that can be used with store-authenticated commands such as \`shopify store execute\`.
To list stores in a Shopify organization, run \`shopify store list\`.`

static description = this.descriptionWithoutMarkdown()

static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json']

static flags = {
...globalFlags,
...jsonFlag,
}

async run(): Promise<void> {
const {flags} = await this.parse(AuthList)
const result = listStoreAuthSessions()

writeStoreAuthListResult(result, flags.json ? 'json' : 'text')
}
}
110 changes: 110 additions & 0 deletions packages/store/src/cli/services/store/auth/list-result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {writeStoreAuthListResult} from './list-result.js'
import {beforeEach, describe, expect, test} from 'vitest'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'

describe('writeStoreAuthListResult', () => {
beforeEach(() => {
mockAndCaptureOutput().clear()
})

test('renders direct store-auth sessions with subdomain, account, scopes, and connected date', () => {
const output = mockAndCaptureOutput()

writeStoreAuthListResult(
{
sessions: [
{
kind: 'store',
store: 'my-shop.myshopify.com',
userId: '42',
scopes: ['read_products', 'write_products'],
connectedAt: '2026-05-22T00:00:00Z',
associatedUser: {id: 42, email: 'merchant@example.com'},
},
],
},
'text',
)

expect(output.info()).toContain('Stores authenticated directly with `shopify store auth`')
expect(output.info()).toContain('Store')
expect(output.info()).toContain('my-shop')
expect(output.info()).not.toContain('my-shop.myshopify.com')
expect(output.info()).toContain('merchant@example.com')
expect(output.info()).toContain('read_products, write_products')
expect(output.info()).toContain('May 22, 2026')
expect(output.info()).toContain('shopify store list')
})

test('renders account names when associated user email is unavailable', () => {
const output = mockAndCaptureOutput()

writeStoreAuthListResult(
{
sessions: [
{
kind: 'store',
store: 'my-shop.myshopify.com',
userId: '42',
scopes: ['read_products'],
connectedAt: '2026-05-22T00:00:00Z',
associatedUser: {id: 42, firstName: 'Merchant', lastName: 'User'},
},
],
},
'text',
)

expect(output.info()).toContain('Merchant User')
})

test('renders user ids when associated user metadata is unavailable', () => {
const output = mockAndCaptureOutput()

writeStoreAuthListResult(
{
sessions: [
{
kind: 'store',
store: 'my-shop.myshopify.com',
userId: '42',
scopes: ['read_products'],
connectedAt: '2026-05-22T00:00:00Z',
},
],
},
'text',
)

expect(output.info()).toContain('42')
})

test('renders an empty state with auth and organization-list guidance', () => {
const output = mockAndCaptureOutput()

writeStoreAuthListResult({sessions: []}, 'text')

expect(output.info()).toContain('No stores are authenticated directly with `shopify store auth`.')
expect(output.info()).toContain('shopify store auth --store <domain> --scopes <scopes>')
expect(output.info()).toContain('shopify store list')
})

test('writes a deterministic JSON document', () => {
const output = mockAndCaptureOutput()
const result = {
sessions: [
{
kind: 'store' as const,
store: 'shop.myshopify.com',
userId: '42',
scopes: ['read_products'],
connectedAt: '2026-05-22T00:00:00Z',
},
],
}

writeStoreAuthListResult(result, 'json')

expect(JSON.parse(output.output())).toEqual(result)
})
})
Loading
Loading