From 7661d2a14ba810d57859226bfb504684186d7962 Mon Sep 17 00:00:00 2001 From: Donald Merand Date: Mon, 15 Jun 2026 10:30:28 -0400 Subject: [PATCH 1/3] Add auth list for store auth sessions --- .changeset/auth-list-store-sessions.md | 6 ++ .../interfaces/auth-list.interface.ts | 24 +++++++ packages/cli/README.md | 28 ++++++++ packages/cli/oclif.manifest.json | 50 ++++++++++++++ .../store/src/cli/commands/auth/list.test.ts | 32 +++++++++ packages/store/src/cli/commands/auth/list.ts | 29 ++++++++ .../services/store/auth/list-result.test.ts | 67 +++++++++++++++++++ .../cli/services/store/auth/list-result.ts | 56 ++++++++++++++++ .../src/cli/services/store/auth/list.test.ts | 36 ++++++++++ .../store/src/cli/services/store/auth/list.ts | 31 +++++++++ .../cli/services/store/list/result.test.ts | 3 + .../src/cli/services/store/list/result.ts | 13 +++- packages/store/src/index.ts | 2 + 13 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 .changeset/auth-list-store-sessions.md create mode 100644 docs-shopify.dev/commands/interfaces/auth-list.interface.ts create mode 100644 packages/store/src/cli/commands/auth/list.test.ts create mode 100644 packages/store/src/cli/commands/auth/list.ts create mode 100644 packages/store/src/cli/services/store/auth/list-result.test.ts create mode 100644 packages/store/src/cli/services/store/auth/list-result.ts create mode 100644 packages/store/src/cli/services/store/auth/list.test.ts create mode 100644 packages/store/src/cli/services/store/auth/list.ts diff --git a/.changeset/auth-list-store-sessions.md b/.changeset/auth-list-store-sessions.md new file mode 100644 index 0000000000..4ebb8630e6 --- /dev/null +++ b/.changeset/auth-list-store-sessions.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli': minor +'@shopify/store': minor +--- + +Add `shopify auth list` to list stores authenticated directly with `shopify store auth`. diff --git a/docs-shopify.dev/commands/interfaces/auth-list.interface.ts b/docs-shopify.dev/commands/interfaces/auth-list.interface.ts new file mode 100644 index 0000000000..efa3d34319 --- /dev/null +++ b/docs-shopify.dev/commands/interfaces/auth-list.interface.ts @@ -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'?: '' +} diff --git a/packages/cli/README.md b/packages/cli/README.md index 2567b48fb1..86af66e549 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -30,6 +30,7 @@ * [`shopify app release --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) @@ -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. diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index bd0e911e76..ccbf7c67d2 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -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": [ ], diff --git a/packages/store/src/cli/commands/auth/list.test.ts b/packages/store/src/cli/commands/auth/list.test.ts new file mode 100644 index 0000000000..5a997090a0 --- /dev/null +++ b/packages/store/src/cli/commands/auth/list.test.ts @@ -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') + }) +}) diff --git a/packages/store/src/cli/commands/auth/list.ts b/packages/store/src/cli/commands/auth/list.ts new file mode 100644 index 0000000000..beb42d71e2 --- /dev/null +++ b/packages/store/src/cli/commands/auth/list.ts @@ -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 { + const {flags} = await this.parse(AuthList) + const result = listStoreAuthSessions() + + writeStoreAuthListResult(result, flags.json ? 'json' : 'text') + } +} diff --git a/packages/store/src/cli/services/store/auth/list-result.test.ts b/packages/store/src/cli/services/store/auth/list-result.test.ts new file mode 100644 index 0000000000..ec12e79590 --- /dev/null +++ b/packages/store/src/cli/services/store/auth/list-result.test.ts @@ -0,0 +1,67 @@ +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 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 --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) + }) +}) diff --git a/packages/store/src/cli/services/store/auth/list-result.ts b/packages/store/src/cli/services/store/auth/list-result.ts new file mode 100644 index 0000000000..b9d6673df2 --- /dev/null +++ b/packages/store/src/cli/services/store/auth/list-result.ts @@ -0,0 +1,56 @@ +import {type StoreAuthListEntry, type StoreAuthListResult} from './list.js' +import {outputInfo, outputResult} from '@shopify/cli-kit/node/output' +import {renderTable} from '@shopify/cli-kit/node/ui' +import {formatShortDate} from '@shopify/cli-kit/common/string' +import {extractSubdomain} from '@shopify/cli-kit/common/url' + +export function writeStoreAuthListResult(result: StoreAuthListResult, format: 'text' | 'json'): void { + if (format === 'json') { + outputResult(JSON.stringify({sessions: result.sessions}, null, 2)) + return + } + + renderTextResult(result) +} + +function renderTextResult(result: StoreAuthListResult): void { + if (result.sessions.length === 0) { + outputInfo(emptyStateMessage()) + return + } + + outputInfo('Stores authenticated directly with `shopify store auth`:') + renderTable({ + rows: result.sessions.map((session) => ({ + store: extractSubdomain(session.store) ?? session.store, + account: accountLabel(session), + scopes: session.scopes.join(', '), + connected: formatShortDate(session.connectedAt), + })), + columns: { + store: {header: 'Store'}, + account: {header: 'Account'}, + scopes: {header: 'Scopes'}, + connected: {header: 'Connected'}, + }, + }) + outputInfo('To list stores in a Shopify organization, run `shopify store list`.') +} + +function emptyStateMessage(): string { + return [ + 'No stores are authenticated directly with `shopify store auth`.', + '', + 'Run `shopify store auth --store --scopes ` to authenticate a store.', + 'Run `shopify store list` to list stores in a Shopify organization.', + ].join('\n') +} + +function accountLabel(session: StoreAuthListEntry): string { + if (session.associatedUser?.email) return session.associatedUser.email + + const name = [session.associatedUser?.firstName, session.associatedUser?.lastName].filter(Boolean).join(' ') + if (name) return name + + return session.userId +} diff --git a/packages/store/src/cli/services/store/auth/list.test.ts b/packages/store/src/cli/services/store/auth/list.test.ts new file mode 100644 index 0000000000..7f61ebcc5e --- /dev/null +++ b/packages/store/src/cli/services/store/auth/list.test.ts @@ -0,0 +1,36 @@ +import {listStoreAuthSessions} from './list.js' +import {listStoredStoreAuthSummaries} from './stored-auth.js' +import {describe, expect, test, vi} from 'vitest' + +vi.mock('./stored-auth.js') + +describe('listStoreAuthSessions', () => { + test('projects stored store auth summaries into typed auth sessions', () => { + vi.mocked(listStoredStoreAuthSummaries).mockReturnValue([ + { + store: 'shop.myshopify.com', + userId: '42', + scopes: ['read_products'], + acquiredAt: '2026-03-27T00:00:00.000Z', + expiresAt: '2026-03-28T00:00:00.000Z', + refreshTokenExpiresAt: '2026-04-28T00:00:00.000Z', + associatedUser: {id: 42, email: 'merchant@example.com'}, + }, + ]) + + expect(listStoreAuthSessions()).toEqual({ + sessions: [ + { + kind: 'store', + store: 'shop.myshopify.com', + userId: '42', + scopes: ['read_products'], + connectedAt: '2026-03-27T00:00:00.000Z', + expiresAt: '2026-03-28T00:00:00.000Z', + refreshTokenExpiresAt: '2026-04-28T00:00:00.000Z', + associatedUser: {id: 42, email: 'merchant@example.com'}, + }, + ], + }) + }) +}) diff --git a/packages/store/src/cli/services/store/auth/list.ts b/packages/store/src/cli/services/store/auth/list.ts new file mode 100644 index 0000000000..1a28e07d98 --- /dev/null +++ b/packages/store/src/cli/services/store/auth/list.ts @@ -0,0 +1,31 @@ +import {listStoredStoreAuthSummaries, type StoredStoreAuthSummary} from './stored-auth.js' + +export interface StoreAuthListEntry { + kind: 'store' + store: string + userId: string + scopes: string[] + connectedAt: string + expiresAt?: string + refreshTokenExpiresAt?: string + associatedUser?: StoredStoreAuthSummary['associatedUser'] +} + +export interface StoreAuthListResult { + sessions: StoreAuthListEntry[] +} + +export function listStoreAuthSessions(): StoreAuthListResult { + return { + sessions: listStoredStoreAuthSummaries().map((summary) => ({ + kind: 'store', + store: summary.store, + userId: summary.userId, + scopes: summary.scopes, + connectedAt: summary.acquiredAt, + ...(summary.expiresAt ? {expiresAt: summary.expiresAt} : {}), + ...(summary.refreshTokenExpiresAt ? {refreshTokenExpiresAt: summary.refreshTokenExpiresAt} : {}), + ...(summary.associatedUser ? {associatedUser: summary.associatedUser} : {}), + })), + } +} diff --git a/packages/store/src/cli/services/store/list/result.test.ts b/packages/store/src/cli/services/store/list/result.test.ts index a2fbb0506d..3290809c9d 100644 --- a/packages/store/src/cli/services/store/list/result.test.ts +++ b/packages/store/src/cli/services/store/list/result.test.ts @@ -38,6 +38,7 @@ describe('writeStoreListResult', () => { expect(output.info()).toContain('My Shop') expect(output.info()).toContain('Dev') expect(output.info()).toContain('May 22, 2026') + expect(output.info()).toContain('shopify auth list') }) test('renders the subdomain handle for non-myshopify hosts (local dev)', () => { @@ -78,6 +79,7 @@ describe('writeStoreListResult', () => { expect(output.warn()).toContain("Couldn't resolve a Shopify account for the current CLI session.") expect(output.info()).toContain('No stores were returned for the current CLI session.') + expect(output.info()).toContain('shopify auth list') }) test('renders the selected organization empty state', () => { @@ -94,6 +96,7 @@ describe('writeStoreListResult', () => { writeStoreListResult({source: 'organization', stores: []}, 'text') expect(output.info()).toContain('No stores found in your Shopify organization.') + expect(output.info()).toContain('shopify auth list') }) test('emits a {stores, source, organization} JSON document on stdout', () => { diff --git a/packages/store/src/cli/services/store/list/result.ts b/packages/store/src/cli/services/store/list/result.ts index 43d38d58a0..c94fe951b8 100644 --- a/packages/store/src/cli/services/store/list/result.ts +++ b/packages/store/src/cli/services/store/list/result.ts @@ -48,6 +48,7 @@ function renderTextResult(result: ListStoresResult): void { } renderOrganizationTable(result.stores) + outputInfo('To list stores authenticated directly with `shopify store auth`, run `shopify auth list`.') } function renderOrganizationTable(stores: StoreListEntry[]): void { @@ -69,14 +70,22 @@ function renderOrganizationTable(stores: StoreListEntry[]): void { function emptyStateMessage(result: ListStoresResult): string { if (result.notice) { - return 'No stores were returned for the current CLI session.' + return [ + 'No stores were returned for the current CLI session.', + '', + 'Run `shopify auth list` to list stores authenticated directly with `shopify store auth`.', + ].join('\n') } if (result.organization) { return `No stores found in ${result.organization.name}.` } - return 'No stores found in your Shopify organization.' + return [ + 'No stores found in your Shopify organization.', + '', + 'Run `shopify auth list` to list stores authenticated directly with `shopify store auth`.', + ].join('\n') } function subdomainFor(store: string): string { diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index a4f1ac0586..6d49fbfe8a 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -1,3 +1,4 @@ +import AuthList from './cli/commands/auth/list.js' import StoreAuth from './cli/commands/store/auth.js' import StoreCreateDev from './cli/commands/store/create/dev.js' import StoreExecute from './cli/commands/store/execute.js' @@ -5,6 +6,7 @@ import StoreInfo from './cli/commands/store/info.js' import StoreList from './cli/commands/store/list.js' const COMMANDS = { + 'auth:list': AuthList, 'store:auth': StoreAuth, 'store:create:dev': StoreCreateDev, 'store:execute': StoreExecute, From 0f253e0308d1531bab417cb9b1954f45f65413fa Mon Sep 17 00:00:00 2001 From: Donald Merand Date: Mon, 15 Jun 2026 10:40:08 -0400 Subject: [PATCH 2/3] Refresh generated docs data for auth list --- .../generated/generated_docs_data_v2.json | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 9f8e45bc65..5087672b80 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -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 '?: string\n\n /**\n * The API Version of the webhook topic.\n * @environment SHOPIFY_FLAG_API_VERSION\n */\n '--api-version '?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id '?: 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 '?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config '?: 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 '?: 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 '?: 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 '?: 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", From 35152cf2fe26abcb98abee5d1016175336414959 Mon Sep 17 00:00:00 2001 From: Donald Merand Date: Mon, 15 Jun 2026 10:59:26 -0400 Subject: [PATCH 3/3] Cover auth list text fallbacks --- packages/e2e/data/snapshots/commands.txt | 1 + .../services/store/auth/list-result.test.ts | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/e2e/data/snapshots/commands.txt b/packages/e2e/data/snapshots/commands.txt index aef4be50e0..c97c486300 100644 --- a/packages/e2e/data/snapshots/commands.txt +++ b/packages/e2e/data/snapshots/commands.txt @@ -37,6 +37,7 @@ │ └─ webhook │ └─ trigger ├─ auth +│ ├─ list │ ├─ login │ └─ logout ├─ commands diff --git a/packages/store/src/cli/services/store/auth/list-result.test.ts b/packages/store/src/cli/services/store/auth/list-result.test.ts index ec12e79590..41e22b23f2 100644 --- a/packages/store/src/cli/services/store/auth/list-result.test.ts +++ b/packages/store/src/cli/services/store/auth/list-result.test.ts @@ -36,6 +36,49 @@ describe('writeStoreAuthListResult', () => { 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()