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/5] 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/5] 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/5] 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/5] 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/5] 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