From 349800a5d7c95faf7d5410b86faa89b7f1be63b3 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:05:32 -0500 Subject: [PATCH 1/6] Improve status messages for `app bulk execute` task --- .../generated/bulk-operation-run-mutation.ts | 2 ++ .../generated/bulk-operation-run-query.ts | 2 ++ .../generated/get-bulk-operation-by-id.ts | 2 ++ .../bulk-operations/generated/types.d.ts | 9 ++++++ .../bulk-operation-run-mutation.graphql | 1 + .../bulk-operation-run-query.graphql | 1 + .../queries/get-bulk-operation-by-id.graphql | 1 + .../format-bulk-operation-status.test.ts | 28 ++++++++++++------- .../format-bulk-operation-status.ts | 6 ++-- 9 files changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.ts index b282c6e3f7..7252cc29a5 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.ts +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.ts @@ -12,6 +12,7 @@ export type BulkOperationRunMutationMutationVariables = Types.Exact<{ export type BulkOperationRunMutationMutation = { bulkOperationRunMutation?: { bulkOperation?: { + type: Types.BulkOperationType completedAt?: unknown | null createdAt: unknown errorCode?: Types.BulkOperationErrorCode | null @@ -81,6 +82,7 @@ export const BulkOperationRunMutation = { selectionSet: { kind: 'SelectionSet', selections: [ + {kind: 'Field', name: {kind: 'Name', value: 'type'}}, {kind: 'Field', name: {kind: 'Name', value: 'completedAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'createdAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'errorCode'}}, diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.ts index 480d02c9f1..c514367800 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.ts +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.ts @@ -10,6 +10,7 @@ export type BulkOperationRunQueryMutationVariables = Types.Exact<{ export type BulkOperationRunQueryMutation = { bulkOperationRunQuery?: { bulkOperation?: { + type: Types.BulkOperationType completedAt?: unknown | null createdAt: unknown errorCode?: Types.BulkOperationErrorCode | null @@ -59,6 +60,7 @@ export const BulkOperationRunQuery = { selectionSet: { kind: 'SelectionSet', selections: [ + {kind: 'Field', name: {kind: 'Name', value: 'type'}}, {kind: 'Field', name: {kind: 'Name', value: 'completedAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'createdAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'errorCode'}}, diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.ts index 6dea755636..eaa3c34352 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.ts +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.ts @@ -9,6 +9,7 @@ export type GetBulkOperationByIdQueryVariables = Types.Exact<{ export type GetBulkOperationByIdQuery = { bulkOperation?: { + type: Types.BulkOperationType completedAt?: unknown | null createdAt: unknown errorCode?: Types.BulkOperationErrorCode | null @@ -50,6 +51,7 @@ export const GetBulkOperationById = { selectionSet: { kind: 'SelectionSet', selections: [ + {kind: 'Field', name: {kind: 'Name', value: 'type'}}, {kind: 'Field', name: {kind: 'Name', value: 'completedAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'createdAt'}}, {kind: 'Field', name: {kind: 'Name', value: 'errorCode'}}, diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts index bdb0cdb668..1b18af2517 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts @@ -188,6 +188,13 @@ export type BulkOperationStatus = /** The bulk operation is runnning. */ | 'RUNNING' +/** The valid values for the bulk operation's type. */ +export type BulkOperationType = + /** The bulk operation is a mutation. */ + | 'MUTATION' + /** The bulk operation is a query. */ + | 'QUERY' + /** Possible error codes that can be returned by `BulkOperationUserError`. */ export type BulkOperationUserErrorCode = /** The input value is invalid. */ @@ -203,6 +210,8 @@ export type BulkOperationsSortKeys = | 'COMPLETED_AT' /** Sort by the `created_at` value. */ | 'CREATED_AT' + /** Sort by the `status` value. */ + | 'STATUS' /** * The possible HTTP methods that can be used when sending a request to upload a file using information from a diff --git a/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-mutation.graphql b/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-mutation.graphql index a0d58086e0..9e03970022 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-mutation.graphql +++ b/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-mutation.graphql @@ -9,6 +9,7 @@ mutation BulkOperationRunMutation( clientIdentifier: $clientIdentifier ) { bulkOperation { + type completedAt createdAt errorCode diff --git a/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-query.graphql b/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-query.graphql index 9922c8acc8..8a2101ade8 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-query.graphql +++ b/packages/app/src/cli/api/graphql/bulk-operations/mutations/bulk-operation-run-query.graphql @@ -3,6 +3,7 @@ mutation BulkOperationRunQuery($query: String!) { query: $query ) { bulkOperation { + type completedAt createdAt errorCode diff --git a/packages/app/src/cli/api/graphql/bulk-operations/queries/get-bulk-operation-by-id.graphql b/packages/app/src/cli/api/graphql/bulk-operations/queries/get-bulk-operation-by-id.graphql index f925b656df..3567e5b487 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/queries/get-bulk-operation-by-id.graphql +++ b/packages/app/src/cli/api/graphql/bulk-operations/queries/get-bulk-operation-by-id.graphql @@ -1,5 +1,6 @@ query GetBulkOperationById($id: ID!) { bulkOperation(id: $id) { + type completedAt createdAt errorCode diff --git a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts index a97fbbdadb..e53d84a2a3 100644 --- a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts +++ b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts @@ -8,54 +8,62 @@ function createMockOperation(overrides: Partial = {}): BulkOperat return { id: 'gid://shopify/BulkOperation/123', status: 'CREATED', + type: 'QUERY', errorCode: null, createdAt: '2024-01-01T00:00:00Z', completedAt: null, objectCount: '0', url: null, + partialDataUrl: null, ...overrides, } } describe('formatBulkOperationStatus', () => { - test('formats RUNNING status with object count', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', objectCount: 42})) - expect(result.value).toContain('Bulk operation in progress...') - expect(result.value).toContain('(42 objects)') + test('formats RUNNING status for query with object count', () => { + const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', type: 'QUERY', objectCount: '42'})) + expect(result.value).toContain('Bulk operation in progress') + expect(result.value).toContain('(42 objects read)') + }) + + test('formats RUNNING status for mutation with object count', () => { + const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', type: 'MUTATION', objectCount: '42'})) + expect(result.value).toContain('Bulk operation in progress') + expect(result.value).toContain('(42 objects written)') }) test('formats CREATED status', () => { const result = formatBulkOperationStatus(createMockOperation({status: 'CREATED'})) - expect(result.value).toBe('Starting...') + expect(result.value).toBe('Starting') }) test('formats COMPLETED status', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'COMPLETED', objectCount: 100})) + const result = formatBulkOperationStatus(createMockOperation({status: 'COMPLETED', objectCount: '100'})) expect(result.value).toContain('Bulk operation succeeded:') expect(result.value).toContain('100 objects') }) test('formats FAILED status with error code', () => { const result = formatBulkOperationStatus( - createMockOperation({status: 'FAILED', objectCount: 10, errorCode: 'ACCESS_DENIED'}), + createMockOperation({status: 'FAILED', objectCount: '10', errorCode: 'ACCESS_DENIED'}), ) expect(result.value).toContain('Bulk operation failed.') expect(result.value).toContain('Error: ACCESS_DENIED') }) test('formats FAILED status without error code', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'FAILED', objectCount: 10, errorCode: null})) + const result = formatBulkOperationStatus(createMockOperation({status: 'FAILED', objectCount: '10', errorCode: null})) expect(result.value).toContain('Bulk operation failed.') expect(result.value).toContain('Error: unknown') }) test('formats CANCELING status', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'CANCELING', objectCount: 5})) + const result = formatBulkOperationStatus(createMockOperation({status: 'CANCELING', objectCount: '5'})) expect(result.value).toBe('Bulk operation canceling...') }) test('formats CANCELED status', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'CANCELED', objectCount: 5})) + const result = formatBulkOperationStatus(createMockOperation({status: 'CANCELED', objectCount: '5'})) expect(result.value).toBe('Bulk operation canceled.') }) diff --git a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts index 08af5687e5..90e5e9871f 100644 --- a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts +++ b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.ts @@ -6,11 +6,11 @@ export function formatBulkOperationStatus( ): TokenizedString { switch (operation.status) { case 'RUNNING': - return outputContent`Bulk operation in progress... ${outputToken.gray( - `(${String(operation.objectCount)} objects)`, + return outputContent`Bulk operation in progress ${outputToken.gray( + `(${String(operation.objectCount)} objects ${operation.type === 'MUTATION' ? 'written' : 'read'})`, )}` case 'CREATED': - return outputContent`Starting...` + return outputContent`Starting` case 'COMPLETED': return outputContent`Bulk operation succeeded: ${outputToken.gray(`${String(operation.objectCount)} objects`)}` case 'FAILED': From cf10d28dfe472aa283a7f1b7410168a497aa9152 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sat, 6 Dec 2025 15:20:34 -0500 Subject: [PATCH 2/6] fix linting error --- .../bulk-operations/format-bulk-operation-status.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts index e53d84a2a3..f20ce88d58 100644 --- a/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts +++ b/packages/app/src/cli/services/bulk-operations/format-bulk-operation-status.test.ts @@ -27,7 +27,9 @@ describe('formatBulkOperationStatus', () => { }) test('formats RUNNING status for mutation with object count', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'RUNNING', type: 'MUTATION', objectCount: '42'})) + const result = formatBulkOperationStatus( + createMockOperation({status: 'RUNNING', type: 'MUTATION', objectCount: '42'}), + ) expect(result.value).toContain('Bulk operation in progress') expect(result.value).toContain('(42 objects written)') }) @@ -52,7 +54,9 @@ describe('formatBulkOperationStatus', () => { }) test('formats FAILED status without error code', () => { - const result = formatBulkOperationStatus(createMockOperation({status: 'FAILED', objectCount: '10', errorCode: null})) + const result = formatBulkOperationStatus( + createMockOperation({status: 'FAILED', objectCount: '10', errorCode: null}), + ) expect(result.value).toContain('Bulk operation failed.') expect(result.value).toContain('Error: unknown') }) From 230006bfed2d0680e99fb001f5e3d35b02bd1f65 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:13:17 -0500 Subject: [PATCH 3/6] fix tests --- .../bulk-operations/bulk-operation-status.test.ts | 8 +++++--- .../bulk-operations/execute-bulk-operation.test.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts index 5930a087c9..4f10cbb137 100644 --- a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts +++ b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts @@ -38,6 +38,7 @@ describe('getBulkOperationStatus', () => { return { bulkOperation: { id: operationId, + type: 'QUERY', status: 'RUNNING', errorCode: null, objectCount: 100, @@ -75,8 +76,8 @@ describe('getBulkOperationStatus', () => { const output = mockAndCaptureOutput() await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) - expect(output.info()).toContain('Bulk operation in progress...') - expect(output.info()).toContain('500 objects') + expect(output.info()).toContain('Bulk operation in progress') + expect(output.info()).toContain('500 objects read') expect(output.info()).toContain('Started') }) @@ -114,7 +115,7 @@ describe('getBulkOperationStatus', () => { const output = mockAndCaptureOutput() await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) - expect(output.info()).toContain('Starting...') + expect(output.info()).toContain('Starting') }) test('renders info banner for canceled operation', async () => { @@ -160,6 +161,7 @@ describe('listBulkOperations', () => { bulkOperations: { nodes: operations.map((op) => ({ id: 'gid://shopify/BulkOperation/123', + type: 'QUERY', status: 'RUNNING', errorCode: null, objectCount: 100, diff --git a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts index c0dd52143b..9ded3297aa 100644 --- a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts +++ b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts @@ -49,6 +49,7 @@ describe('executeBulkOperation', () => { NonNullable['bulkOperation'] > = { id: 'gid://shopify/BulkOperation/123', + type: 'QUERY', status: 'CREATED', errorCode: null, createdAt: '2024-01-01T00:00:00Z', From 3b05ed661a07405fa1ce66b858baa8a1741e5c86 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:19:54 -0500 Subject: [PATCH 4/6] missing codegen updates --- .../cli/api/graphql/app-dev/generated/types.d.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts b/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts index 024de4d7d7..4fd45eb831 100644 --- a/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any, tsdoc/syntax */ +/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention, tsdoc/syntax */ import {JsonMapType} from '@shopify/cli-kit/node/toml' export type Maybe = T | null @@ -15,12 +15,6 @@ export type Scalars = { Boolean: {input: boolean; output: boolean} Int: {input: number; output: number} Float: {input: number; output: number} - /** - * Represents an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)-encoded date and time string. - * For example, 3:50 pm on September 7, 2019 in the time zone of UTC (Coordinated Universal Time) is - * represented as `"2019-09-07T15:50:00Z`". - */ - DateTime: {input: any; output: any} /** * A [JSON](https://www.json.org/json-en.html) object. * @@ -37,12 +31,4 @@ export type Scalars = { * }` */ JSON: {input: JsonMapType | string; output: JsonMapType} - /** - * Represents an [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) and - * [RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987)-compliant URI string. - * - * For example, `"https://example.myshopify.com"` is a valid URL. It includes a scheme (`https`) and a host - * (`example.myshopify.com`). - */ - URL: {input: string; output: string} } From aa765cac39dd07eec349cf01a13996b444abaee1 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sat, 6 Dec 2025 16:28:17 -0500 Subject: [PATCH 5/6] add more missing graphql types --- .../cli/api/graphql/app-dev/generated/types.d.ts | 16 +++++++++++++++- .../graphql/bulk-operations/generated/types.d.ts | 2 -- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts b/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts index 4fd45eb831..024de4d7d7 100644 --- a/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/app-dev/generated/types.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention, tsdoc/syntax */ +/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any, tsdoc/syntax */ import {JsonMapType} from '@shopify/cli-kit/node/toml' export type Maybe = T | null @@ -15,6 +15,12 @@ export type Scalars = { Boolean: {input: boolean; output: boolean} Int: {input: number; output: number} Float: {input: number; output: number} + /** + * Represents an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)-encoded date and time string. + * For example, 3:50 pm on September 7, 2019 in the time zone of UTC (Coordinated Universal Time) is + * represented as `"2019-09-07T15:50:00Z`". + */ + DateTime: {input: any; output: any} /** * A [JSON](https://www.json.org/json-en.html) object. * @@ -31,4 +37,12 @@ export type Scalars = { * }` */ JSON: {input: JsonMapType | string; output: JsonMapType} + /** + * Represents an [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) and + * [RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987)-compliant URI string. + * + * For example, `"https://example.myshopify.com"` is a valid URL. It includes a scheme (`https`) and a host + * (`example.myshopify.com`). + */ + URL: {input: string; output: string} } diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts index 1b18af2517..f52c0a5a9a 100644 --- a/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/types.d.ts @@ -210,8 +210,6 @@ export type BulkOperationsSortKeys = | 'COMPLETED_AT' /** Sort by the `created_at` value. */ | 'CREATED_AT' - /** Sort by the `status` value. */ - | 'STATUS' /** * The possible HTTP methods that can be used when sending a request to upload a file using information from a From ae93c9c870f0889e403bafea2ce344d4c0ceda74 Mon Sep 17 00:00:00 2001 From: Nick Wesselman <27013789+nickwesselman@users.noreply.github.com> Date: Sat, 6 Dec 2025 15:17:06 -0500 Subject: [PATCH 6/6] Update info banners with organization info, and add to status command --- .../app/src/cli/commands/app/bulk/execute.ts | 1 + .../app/src/cli/commands/app/bulk/status.ts | 2 + packages/app/src/cli/commands/app/execute.ts | 1 + .../bulk-operation-status.test.ts | 51 ++++++++++++------- .../bulk-operations/bulk-operation-status.ts | 33 ++++++++++-- .../execute-bulk-operation.test.ts | 26 +++++++++- .../bulk-operations/execute-bulk-operation.ts | 18 ++++--- .../cli/services/execute-operation.test.ts | 19 ++++++- .../app/src/cli/services/execute-operation.ts | 18 ++++--- .../app/src/cli/services/graphql/common.ts | 25 +++++++++ 10 files changed, 155 insertions(+), 39 deletions(-) diff --git a/packages/app/src/cli/commands/app/bulk/execute.ts b/packages/app/src/cli/commands/app/bulk/execute.ts index d54f2d287d..92857c6916 100644 --- a/packages/app/src/cli/commands/app/bulk/execute.ts +++ b/packages/app/src/cli/commands/app/bulk/execute.ts @@ -24,6 +24,7 @@ export default class BulkExecute extends AppLinkedCommand { const {query, appContextResult, store} = await prepareExecuteContext(flags, 'bulk execute') await executeBulkOperation({ + organization: appContextResult.organization, remoteApp: appContextResult.remoteApp, storeFqdn: store.shopDomain, query, diff --git a/packages/app/src/cli/commands/app/bulk/status.ts b/packages/app/src/cli/commands/app/bulk/status.ts index fd3ee4fb72..5923e1e3b1 100644 --- a/packages/app/src/cli/commands/app/bulk/status.ts +++ b/packages/app/src/cli/commands/app/bulk/status.ts @@ -36,12 +36,14 @@ export default class BulkStatus extends AppLinkedCommand { if (flags.id) { await getBulkOperationStatus({ + organization: appContextResult.organization, storeFqdn: store.shopDomain, operationId: flags.id, remoteApp: appContextResult.remoteApp, }) } else { await listBulkOperations({ + organization: appContextResult.organization, storeFqdn: store.shopDomain, remoteApp: appContextResult.remoteApp, }) diff --git a/packages/app/src/cli/commands/app/execute.ts b/packages/app/src/cli/commands/app/execute.ts index 37fb1ffcd8..9dfa85c342 100644 --- a/packages/app/src/cli/commands/app/execute.ts +++ b/packages/app/src/cli/commands/app/execute.ts @@ -21,6 +21,7 @@ export default class Execute extends AppLinkedCommand { const {query, appContextResult, store} = await prepareExecuteContext(flags, 'execute') await executeOperation({ + organization: appContextResult.organization, remoteApp: appContextResult.remoteApp, storeFqdn: store.shopDomain, query, diff --git a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts index 4f10cbb137..2daf2a8196 100644 --- a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts +++ b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.test.ts @@ -1,6 +1,6 @@ import {getBulkOperationStatus, listBulkOperations} from './bulk-operation-status.js' import {GetBulkOperationByIdQuery} from '../../api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js' -import {OrganizationApp} from '../../models/organization.js' +import {OrganizationApp, Organization, OrganizationSource} from '../../models/organization.js' import {ListBulkOperationsQuery} from '../../api/graphql/bulk-operations/generated/list-bulk-operations.js' import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest' import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' @@ -12,6 +12,11 @@ vi.mock('@shopify/cli-kit/node/api/admin') const storeFqdn = 'test-store.myshopify.com' const operationId = 'gid://shopify/BulkOperation/123' +const mockOrganization: Organization = { + id: 'test-org-id', + businessName: 'Test Organization', + source: OrganizationSource.BusinessPlatform, +} const remoteApp = { id: '123', title: 'Test App', @@ -61,7 +66,7 @@ describe('getBulkOperationStatus', () => { ) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.output()).toContain('Bulk operation succeeded:') expect(output.output()).toContain('100 objects') @@ -74,10 +79,11 @@ describe('getBulkOperationStatus', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperation({status: 'RUNNING', objectCount: 500})) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) + expect(output.info()).toContain('Checking bulk operation status.') expect(output.info()).toContain('Bulk operation in progress') - expect(output.info()).toContain('500 objects read') + expect(output.info()).toContain('500 objects') expect(output.info()).toContain('Started') }) @@ -92,7 +98,7 @@ describe('getBulkOperationStatus', () => { ) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.error()).toContain('Error: ACCESS_DENIED') expect(output.error()).toContain('Finished') @@ -103,7 +109,7 @@ describe('getBulkOperationStatus', () => { vi.mocked(adminRequestDoc).mockResolvedValue({bulkOperation: null}) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.error()).toContain('Bulk operation not found.') expect(output.error()).toContain(operationId) @@ -113,7 +119,7 @@ describe('getBulkOperationStatus', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperation({status: 'CREATED', objectCount: 0})) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.info()).toContain('Starting') }) @@ -122,7 +128,7 @@ describe('getBulkOperationStatus', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperation({status: 'CANCELED'})) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.info()).toContain('Bulk operation canceled.') }) @@ -132,7 +138,7 @@ describe('getBulkOperationStatus', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperation({status: 'RUNNING'})) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.output()).toContain('Started') }) @@ -146,7 +152,7 @@ describe('getBulkOperationStatus', () => { ) const output = mockAndCaptureOutput() - await getBulkOperationStatus({storeFqdn, operationId, remoteApp}) + await getBulkOperationStatus({organization: mockOrganization, storeFqdn, operationId, remoteApp}) expect(output.output()).toContain('Finished') }) @@ -196,7 +202,7 @@ describe('listBulkOperations', () => { ) const output = mockAndCaptureOutput() - await listBulkOperations({storeFqdn, remoteApp}) + await listBulkOperations({organization: mockOrganization, storeFqdn, remoteApp}) const outputLinesWithoutTrailingWhitespace = output .output() @@ -206,7 +212,17 @@ describe('listBulkOperations', () => { // terminal width in test environment is quite narrow, so values in the snapshot get wrapped expect(outputLinesWithoutTrailingWhitespace).toMatchInlineSnapshot(` - "ID STATUS COU DATE CREATED DATE RESULTS + "╭─ info ───────────────────────────────────────────────────────────────────────╮ + │ │ + │ Listing bulk operations. │ + │ │ + │ • Organization: Test Organization │ + │ • App: Test App │ + │ • Store: test-store.myshopify.com │ + │ │ + ╰──────────────────────────────────────────────────────────────────────────────╯ + + ID STATUS COU DATE CREATED DATE RESULTS T FINISHED ──────────────── ────── ─── ──────────── ─────────── ─────────────────────────── @@ -224,7 +240,7 @@ describe('listBulkOperations', () => { ) const output = mockAndCaptureOutput() - await listBulkOperations({storeFqdn, remoteApp}) + await listBulkOperations({organization: mockOrganization, storeFqdn, remoteApp}) expect(output.output()).toContain('1.2M') expect(output.output()).toContain('5.5K') @@ -244,7 +260,7 @@ describe('listBulkOperations', () => { ) const output = mockAndCaptureOutput() - await listBulkOperations({storeFqdn, remoteApp}) + await listBulkOperations({organization: mockOrganization, storeFqdn, remoteApp}) expect(output.output()).toContain('download') expect(output.output()).toContain('partial.jsonl') @@ -261,7 +277,7 @@ describe('listBulkOperations', () => { ) const output = mockAndCaptureOutput() - await listBulkOperations({storeFqdn, remoteApp}) + await listBulkOperations({organization: mockOrganization, storeFqdn, remoteApp}) expect(output.output()).toContain('download') expect(output.output()).toContain('results.jsonl') @@ -271,8 +287,9 @@ describe('listBulkOperations', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperationsList([])) const output = mockAndCaptureOutput() - await listBulkOperations({storeFqdn, remoteApp}) + await listBulkOperations({organization: mockOrganization, storeFqdn, remoteApp}) - expect(output.info()).toContain('no bulk operations found in the last 7 days') + expect(output.info()).toContain('Listing bulk operations.') + expect(output.info()).toContain('No bulk operations found in the last 7 days.') }) }) diff --git a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.ts b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.ts index 555f1a3dbc..d404d95092 100644 --- a/packages/app/src/cli/services/bulk-operations/bulk-operation-status.ts +++ b/packages/app/src/cli/services/bulk-operations/bulk-operation-status.ts @@ -4,7 +4,8 @@ import { GetBulkOperationById, GetBulkOperationByIdQuery, } from '../../api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js' -import {OrganizationApp} from '../../models/organization.js' +import {formatOperationInfo} from '../graphql/common.js' +import {OrganizationApp, Organization} from '../../models/organization.js' import { ListBulkOperations, ListBulkOperationsQuery, @@ -21,18 +22,31 @@ import colors from '@shopify/cli-kit/node/colors' const API_VERSION = '2026-01' interface GetBulkOperationStatusOptions { + organization: Organization storeFqdn: string operationId: string remoteApp: OrganizationApp } interface ListBulkOperationsOptions { + organization: Organization storeFqdn: string remoteApp: OrganizationApp } export async function getBulkOperationStatus(options: GetBulkOperationStatusOptions): Promise { - const {storeFqdn, operationId, remoteApp} = options + const {organization, storeFqdn, operationId, remoteApp} = options + + renderInfo({ + headline: 'Checking bulk operation status.', + body: [ + { + list: { + items: formatOperationInfo({organization, remoteApp, storeFqdn, showVersion: false}), + }, + }, + ], + }) const appSecret = remoteApp.apiSecretKeys[0]?.secret if (!appSecret) throw new BugError('No API secret keys found for app') @@ -57,7 +71,18 @@ export async function getBulkOperationStatus(options: GetBulkOperationStatusOpti } export async function listBulkOperations(options: ListBulkOperationsOptions): Promise { - const {storeFqdn, remoteApp} = options + const {organization, storeFqdn, remoteApp} = options + + renderInfo({ + headline: 'Listing bulk operations.', + body: [ + { + list: { + items: formatOperationInfo({organization, remoteApp, storeFqdn, showVersion: false}), + }, + }, + ], + }) const appSecret = remoteApp.apiSecretKeys[0]?.secret if (!appSecret) throw new BugError('No API secret keys found for app') @@ -90,7 +115,7 @@ export async function listBulkOperations(options: ListBulkOperationsOptions): Pr outputNewline() if (operations.length === 0) { - renderInfo({body: 'no bulk operations found in the last 7 days'}) + renderInfo({body: 'No bulk operations found in the last 7 days.'}) } else { renderTable({ rows: operations, diff --git a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts index 9ded3297aa..7769b4023c 100644 --- a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts +++ b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.test.ts @@ -6,7 +6,7 @@ import {downloadBulkOperationResults} from './download-bulk-operation-results.js import {validateApiVersion} from '../graphql/common.js' import {BulkOperationRunQueryMutation} from '../../api/graphql/bulk-operations/generated/bulk-operation-run-query.js' import {BulkOperationRunMutationMutation} from '../../api/graphql/bulk-operations/generated/bulk-operation-run-mutation.js' -import {OrganizationApp} from '../../models/organization.js' +import {OrganizationApp, OrganizationSource} from '../../models/organization.js' import {renderSuccess, renderWarning, renderError, renderInfo} from '@shopify/cli-kit/node/ui' import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs' @@ -36,6 +36,12 @@ vi.mock('@shopify/cli-kit/node/session', async () => { }) describe('executeBulkOperation', () => { + const mockOrganization = { + id: 'test-org-id', + businessName: 'Test Organization', + source: OrganizationSource.BusinessPlatform, + } + const mockRemoteApp = { apiKey: 'test-app-client-id', apiSecretKeys: [{secret: 'test-api-secret'}], @@ -76,6 +82,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -97,6 +104,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -118,6 +126,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query: mutation, @@ -141,6 +150,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query: mutation, @@ -162,6 +172,7 @@ describe('executeBulkOperation', () => { } vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -186,6 +197,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -217,6 +229,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse as any) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query: mutation, @@ -239,6 +252,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query: mutation, @@ -257,6 +271,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -277,6 +292,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -307,6 +323,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue('{"id":"gid://shopify/Product/123"}') await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -339,6 +356,7 @@ describe('executeBulkOperation', () => { }) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -373,6 +391,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue(resultsContent) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -405,6 +424,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue(resultsContent) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -433,6 +453,7 @@ describe('executeBulkOperation', () => { vi.mocked(watchBulkOperation).mockResolvedValue(finishedOperation) await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -457,6 +478,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -482,6 +504,7 @@ describe('executeBulkOperation', () => { vi.mocked(validateApiVersion).mockResolvedValue() await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -506,6 +529,7 @@ describe('executeBulkOperation', () => { vi.mocked(validateApiVersion).mockClear() await executeBulkOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, diff --git a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.ts b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.ts index 75a5dc432f..8895d030ed 100644 --- a/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.ts +++ b/packages/app/src/cli/services/bulk-operations/execute-bulk-operation.ts @@ -3,8 +3,13 @@ import {runBulkOperationMutation} from './run-mutation.js' import {watchBulkOperation, type BulkOperation} from './watch-bulk-operation.js' import {formatBulkOperationStatus} from './format-bulk-operation-status.js' import {downloadBulkOperationResults} from './download-bulk-operation-results.js' -import {createAdminSessionAsApp, validateSingleOperation, validateApiVersion} from '../graphql/common.js' -import {OrganizationApp} from '../../models/organization.js' +import { + createAdminSessionAsApp, + validateSingleOperation, + validateApiVersion, + formatOperationInfo, +} from '../graphql/common.js' +import {OrganizationApp, Organization} from '../../models/organization.js' import {renderSuccess, renderInfo, renderError, renderWarning, TokenItem} from '@shopify/cli-kit/node/ui' import {outputContent, outputToken, outputResult} from '@shopify/cli-kit/node/output' import {AbortError, BugError} from '@shopify/cli-kit/node/error' @@ -13,6 +18,7 @@ import {parse} from 'graphql' import {readFile, writeFile, fileExists} from '@shopify/cli-kit/node/fs' interface ExecuteBulkOperationInput { + organization: Organization remoteApp: OrganizationApp storeFqdn: string query: string @@ -41,7 +47,7 @@ async function parseVariablesToJsonl(variables?: string[], variableFile?: string } export async function executeBulkOperation(input: ExecuteBulkOperationInput): Promise { - const {remoteApp, storeFqdn, query, variables, variableFile, outputFile, watch = false, version} = input + const {organization, remoteApp, storeFqdn, query, variables, variableFile, outputFile, watch = false, version} = input const adminSession = await createAdminSessionAsApp(remoteApp, storeFqdn) @@ -56,11 +62,7 @@ export async function executeBulkOperation(input: ExecuteBulkOperationInput): Pr body: [ { list: { - items: [ - `App: ${remoteApp.title}`, - `Store: ${storeFqdn}`, - `API version: ${version || 'default (latest stable)'}`, - ], + items: formatOperationInfo({organization, remoteApp, storeFqdn, version}), }, }, ], diff --git a/packages/app/src/cli/services/execute-operation.test.ts b/packages/app/src/cli/services/execute-operation.test.ts index fdbb538a4a..de5571b26d 100644 --- a/packages/app/src/cli/services/execute-operation.test.ts +++ b/packages/app/src/cli/services/execute-operation.test.ts @@ -1,6 +1,6 @@ import {executeOperation} from './execute-operation.js' import {createAdminSessionAsApp, validateApiVersion} from './graphql/common.js' -import {OrganizationApp} from '../models/organization.js' +import {OrganizationApp, OrganizationSource} from '../models/organization.js' import {renderSuccess, renderError, renderSingleTask} from '@shopify/cli-kit/node/ui' import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin' import {ClientError} from 'graphql-request' @@ -15,6 +15,12 @@ vi.mock('@shopify/cli-kit/node/api/admin') vi.mock('@shopify/cli-kit/node/fs') describe('executeOperation', () => { + const mockOrganization = { + id: 'test-org-id', + businessName: 'Test Organization', + source: OrganizationSource.BusinessPlatform, + } + const mockRemoteApp = { apiKey: 'test-app-client-id', apiSecretKeys: [{secret: 'test-api-secret'}], @@ -41,6 +47,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockResult) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -64,6 +71,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockResult) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -83,6 +91,7 @@ describe('executeOperation', () => { await expect( executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -101,6 +110,7 @@ describe('executeOperation', () => { vi.mocked(validateApiVersion).mockResolvedValue() await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -122,6 +132,7 @@ describe('executeOperation', () => { vi.mocked(validateApiVersion).mockClear() await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -138,6 +149,7 @@ describe('executeOperation', () => { const mockOutput = mockAndCaptureOutput() await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -156,6 +168,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockResult) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -178,6 +191,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockResult) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -197,6 +211,7 @@ describe('executeOperation', () => { await expect( executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -213,6 +228,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockResolvedValue(mockResult) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, @@ -236,6 +252,7 @@ describe('executeOperation', () => { vi.mocked(adminRequestDoc).mockRejectedValue(clientError) await executeOperation({ + organization: mockOrganization, remoteApp: mockRemoteApp, storeFqdn, query, diff --git a/packages/app/src/cli/services/execute-operation.ts b/packages/app/src/cli/services/execute-operation.ts index 6c8c04da1c..a84112bb54 100644 --- a/packages/app/src/cli/services/execute-operation.ts +++ b/packages/app/src/cli/services/execute-operation.ts @@ -1,5 +1,10 @@ -import {createAdminSessionAsApp, validateSingleOperation, validateApiVersion} from './graphql/common.js' -import {OrganizationApp} from '../models/organization.js' +import { + createAdminSessionAsApp, + validateSingleOperation, + validateApiVersion, + formatOperationInfo, +} from './graphql/common.js' +import {OrganizationApp, Organization} from '../models/organization.js' import {renderSuccess, renderError, renderInfo, renderSingleTask} from '@shopify/cli-kit/node/ui' import {outputContent, outputToken, outputResult} from '@shopify/cli-kit/node/output' import {AbortError} from '@shopify/cli-kit/node/error' @@ -9,6 +14,7 @@ import {parse} from 'graphql' import {writeFile} from '@shopify/cli-kit/node/fs' interface ExecuteOperationInput { + organization: Organization remoteApp: OrganizationApp storeFqdn: string query: string @@ -32,18 +38,14 @@ async function parseVariables(variables?: string): Promise<{[key: string]: unkno } export async function executeOperation(input: ExecuteOperationInput): Promise { - const {remoteApp, storeFqdn, query, variables, version, outputFile} = input + const {organization, remoteApp, storeFqdn, query, variables, version, outputFile} = input renderInfo({ headline: 'Executing GraphQL operation.', body: [ { list: { - items: [ - `App: ${remoteApp.title}`, - `Store: ${storeFqdn}`, - `API version: ${version ?? 'default (latest stable)'}`, - ], + items: formatOperationInfo({organization, remoteApp, storeFqdn, version}), }, }, ], diff --git a/packages/app/src/cli/services/graphql/common.ts b/packages/app/src/cli/services/graphql/common.ts index 24474cf3f0..503339b0a0 100644 --- a/packages/app/src/cli/services/graphql/common.ts +++ b/packages/app/src/cli/services/graphql/common.ts @@ -67,3 +67,28 @@ export async function validateApiVersion( throw new AbortError(`${firstLine}\n${secondLine}`) } + +/** + * Creates formatted info list items for GraphQL operations. + * Includes organization, app, store, and API version information. + * + * @param options - The operation context information + * @returns Array of formatted strings for display + */ +export function formatOperationInfo(options: { + organization: {businessName: string} + remoteApp: {title: string} + storeFqdn: string + version?: string + showVersion?: boolean +}): string[] { + const {organization, remoteApp, storeFqdn, version, showVersion = true} = options + + const items = [`Organization: ${organization.businessName}`, `App: ${remoteApp.title}`, `Store: ${storeFqdn}`] + + if (showVersion) { + items.push(`API version: ${version ?? 'default (latest stable)'}`) + } + + return items +}