From 4e9e34a0fd618c120c38fda38afb7788f05cfedb Mon Sep 17 00:00:00 2001 From: Jordan Verasamy Date: Tue, 25 Nov 2025 14:07:45 -0800 Subject: [PATCH 1/3] If --id is not provided to `shopify app bulk status`, show all bulk operations --- .../generated/list-bulk-operations.ts | 114 ++++++++++++++++ .../bulk-operations/generated/types.d.ts | 7 + .../queries/list-bulk-operations.graphql | 14 ++ .../app/src/cli/commands/app/bulk/status.ts | 26 ++-- .../bulk-operation-status.test.ts | 126 +++++++++++++++++- .../bulk-operations/bulk-operation-status.ts | 80 ++++++++++- packages/cli/oclif.manifest.json | 7 +- 7 files changed, 357 insertions(+), 17 deletions(-) create mode 100644 packages/app/src/cli/api/graphql/bulk-operations/generated/list-bulk-operations.ts create mode 100644 packages/app/src/cli/api/graphql/bulk-operations/queries/list-bulk-operations.graphql diff --git a/packages/app/src/cli/api/graphql/bulk-operations/generated/list-bulk-operations.ts b/packages/app/src/cli/api/graphql/bulk-operations/generated/list-bulk-operations.ts new file mode 100644 index 00000000000..416720528e6 --- /dev/null +++ b/packages/app/src/cli/api/graphql/bulk-operations/generated/list-bulk-operations.ts @@ -0,0 +1,114 @@ +/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-redundant-type-constituents */ +import * as Types from './types.js' + +import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core' + +export type ListBulkOperationsQueryVariables = Types.Exact<{ + query?: Types.InputMaybe + first: Types.Scalars['Int']['input'] + sortKey: Types.BulkOperationsSortKeys + reverse: Types.Scalars['Boolean']['input'] +}> + +export type ListBulkOperationsQuery = { + bulkOperations: { + nodes: { + id: string + status: Types.BulkOperationStatus + errorCode?: Types.BulkOperationErrorCode | null + objectCount: unknown + createdAt: unknown + completedAt?: unknown | null + url?: string | null + partialDataUrl?: string | null + }[] + } +} + +export const ListBulkOperations = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: {kind: 'Name', value: 'ListBulkOperations'}, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: {kind: 'Variable', name: {kind: 'Name', value: 'query'}}, + type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}, + }, + { + kind: 'VariableDefinition', + variable: {kind: 'Variable', name: {kind: 'Name', value: 'first'}}, + type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'Int'}}}, + }, + { + kind: 'VariableDefinition', + variable: {kind: 'Variable', name: {kind: 'Name', value: 'sortKey'}}, + type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'BulkOperationsSortKeys'}}}, + }, + { + kind: 'VariableDefinition', + variable: {kind: 'Variable', name: {kind: 'Name', value: 'reverse'}}, + type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'Boolean'}}}, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: {kind: 'Name', value: 'bulkOperations'}, + arguments: [ + { + kind: 'Argument', + name: {kind: 'Name', value: 'first'}, + value: {kind: 'Variable', name: {kind: 'Name', value: 'first'}}, + }, + { + kind: 'Argument', + name: {kind: 'Name', value: 'query'}, + value: {kind: 'Variable', name: {kind: 'Name', value: 'query'}}, + }, + { + kind: 'Argument', + name: {kind: 'Name', value: 'sortKey'}, + value: {kind: 'Variable', name: {kind: 'Name', value: 'sortKey'}}, + }, + { + kind: 'Argument', + name: {kind: 'Name', value: 'reverse'}, + value: {kind: 'Variable', name: {kind: 'Name', value: 'reverse'}}, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: {kind: 'Name', value: 'nodes'}, + selectionSet: { + kind: 'SelectionSet', + selections: [ + {kind: 'Field', name: {kind: 'Name', value: 'id'}}, + {kind: 'Field', name: {kind: 'Name', value: 'status'}}, + {kind: 'Field', name: {kind: 'Name', value: 'errorCode'}}, + {kind: 'Field', name: {kind: 'Name', value: 'objectCount'}}, + {kind: 'Field', name: {kind: 'Name', value: 'createdAt'}}, + {kind: 'Field', name: {kind: 'Name', value: 'completedAt'}}, + {kind: 'Field', name: {kind: 'Name', value: 'url'}}, + {kind: 'Field', name: {kind: 'Name', value: 'partialDataUrl'}}, + {kind: 'Field', name: {kind: 'Name', value: '__typename'}}, + ], + }, + }, + {kind: 'Field', name: {kind: 'Name', value: '__typename'}}, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode 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 95a968443ca..bdb0cdb668f 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 @@ -197,6 +197,13 @@ export type BulkOperationUserErrorCode = /** A bulk operation is already in progress. */ | 'OPERATION_IN_PROGRESS' +/** The set of valid sort keys for the BulkOperations query. */ +export type BulkOperationsSortKeys = + /** Sort by the `completed_at` value. */ + | 'COMPLETED_AT' + /** Sort by the `created_at` value. */ + | 'CREATED_AT' + /** * The possible HTTP methods that can be used when sending a request to upload a file using information from a * [StagedMediaUploadTarget](https://shopify.dev/api/admin-graphql/latest/objects/StagedMediaUploadTarget). diff --git a/packages/app/src/cli/api/graphql/bulk-operations/queries/list-bulk-operations.graphql b/packages/app/src/cli/api/graphql/bulk-operations/queries/list-bulk-operations.graphql new file mode 100644 index 00000000000..0342db3e319 --- /dev/null +++ b/packages/app/src/cli/api/graphql/bulk-operations/queries/list-bulk-operations.graphql @@ -0,0 +1,14 @@ +query ListBulkOperations($query: String, $first: Int!, $sortKey: BulkOperationsSortKeys!, $reverse: Boolean!) { + bulkOperations(first: $first, query: $query, sortKey: $sortKey, reverse: $reverse) { + nodes { + id + status + errorCode + objectCount + createdAt + completedAt + url + partialDataUrl + } + } +} diff --git a/packages/app/src/cli/commands/app/bulk/status.ts b/packages/app/src/cli/commands/app/bulk/status.ts index a8af5e95811..c9a022d43e9 100644 --- a/packages/app/src/cli/commands/app/bulk/status.ts +++ b/packages/app/src/cli/commands/app/bulk/status.ts @@ -2,15 +2,15 @@ import {appFlags} from '../../../flags.js' import AppLinkedCommand, {AppLinkedCommandOutput} from '../../../utilities/app-linked-command.js' import {linkedAppContext} from '../../../services/app-context.js' import {storeContext} from '../../../services/store-context.js' -import {getBulkOperationStatus} from '../../../services/bulk-operations/bulk-operation-status.js' +import {getBulkOperationStatus, listBulkOperations} from '../../../services/bulk-operations/bulk-operation-status.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn' export default class BulkStatus extends AppLinkedCommand { - static summary = 'Check the status of a bulk operation.' + static summary = 'Check the status of bulk operations.' - static description = 'Check the status of a bulk operation by ID.' + static description = 'Check the status of a specific bulk operation by ID, or list all recent bulk operations.' static hidden = true @@ -18,9 +18,8 @@ export default class BulkStatus extends AppLinkedCommand { ...globalFlags, ...appFlags, id: Flags.string({ - description: 'The bulk operation ID.', + description: 'The bulk operation ID. If not provided, lists all recent bulk operations.', env: 'SHOPIFY_FLAG_ID', - required: true, }), store: Flags.string({ char: 's', @@ -46,11 +45,18 @@ export default class BulkStatus extends AppLinkedCommand { forceReselectStore: flags.reset, }) - await getBulkOperationStatus({ - storeFqdn: store.shopDomain, - operationId: flags.id, - remoteApp: appContextResult.remoteApp, - }) + if (flags.id) { + await getBulkOperationStatus({ + storeFqdn: store.shopDomain, + operationId: flags.id, + remoteApp: appContextResult.remoteApp, + }) + } else { + await listBulkOperations({ + storeFqdn: store.shopDomain, + remoteApp: appContextResult.remoteApp, + }) + } return {app: appContextResult.app} } 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 4251c3ff9de..5930a087c99 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,7 @@ -import {getBulkOperationStatus} from './bulk-operation-status.js' +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 {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' import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin' @@ -150,3 +151,126 @@ describe('getBulkOperationStatus', () => { }) }) }) + +describe('listBulkOperations', () => { + function mockBulkOperationsList( + operations: Partial>[], + ): ListBulkOperationsQuery { + return { + bulkOperations: { + nodes: operations.map((op) => ({ + id: 'gid://shopify/BulkOperation/123', + status: 'RUNNING', + errorCode: null, + objectCount: 100, + createdAt: new Date().toISOString(), + completedAt: null, + url: null, + partialDataUrl: null, + ...op, + })), + }, + } + } + + test('renders table with bulk operations', async () => { + vi.mocked(adminRequestDoc).mockResolvedValue( + mockBulkOperationsList([ + { + id: 'gid://shopify/BulkOperation/1', + status: 'COMPLETED', + objectCount: 123500, + createdAt: '2025-11-10T12:37:52Z', + completedAt: '2025-11-10T16:37:12Z', + url: 'https://example.com/results.jsonl', + }, + { + id: 'gid://shopify/BulkOperation/2', + status: 'RUNNING', + objectCount: 100, + createdAt: '2025-11-11T15:37:52Z', + }, + ]), + ) + + const output = mockAndCaptureOutput() + await listBulkOperations({storeFqdn, remoteApp}) + + const outputLinesWithoutTrailingWhitespace = output + .output() + .split('\n') + .map((line) => line.trimEnd()) + .join('\n') + + // 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 + T FINISHED + + ──────────────── ────── ─── ──────────── ─────────── ─────────────────────────── + ──────────── ── ── ─────── ─────── ─────────────────── + gid://shopify/Bu COMPLE 123 2025-11-10 2025-11-10 download ( https://example. + kOperation/1 ED 5K 12:37:52 16:37:12 com/results.jsonl ) + gid://shopify/Bu RUNNIN 100 2025-11-11 + kOperation/2 15:37:52" + `) + }) + + test('formats large counts as thousands or millions for readability', async () => { + vi.mocked(adminRequestDoc).mockResolvedValue( + mockBulkOperationsList([{objectCount: 1200000}, {objectCount: 5500}, {objectCount: 42}]), + ) + + const output = mockAndCaptureOutput() + await listBulkOperations({storeFqdn, remoteApp}) + + expect(output.output()).toContain('1.2M') + expect(output.output()).toContain('5.5K') + expect(output.output()).toContain('42') + }) + + test('shows download for failed operations with partial results', async () => { + vi.mocked(adminRequestDoc).mockResolvedValue( + mockBulkOperationsList([ + { + status: 'FAILED', + errorCode: 'ACCESS_DENIED', + partialDataUrl: 'https://example.com/partial.jsonl', + completedAt: '2025-11-10T16:37:12Z', + }, + ]), + ) + + const output = mockAndCaptureOutput() + await listBulkOperations({storeFqdn, remoteApp}) + + expect(output.output()).toContain('download') + expect(output.output()).toContain('partial.jsonl') + }) + + test('shows download for completed operations with results', async () => { + vi.mocked(adminRequestDoc).mockResolvedValue( + mockBulkOperationsList([ + { + status: 'COMPLETED', + url: 'https://example.com/results.jsonl', + }, + ]), + ) + + const output = mockAndCaptureOutput() + await listBulkOperations({storeFqdn, remoteApp}) + + expect(output.output()).toContain('download') + expect(output.output()).toContain('results.jsonl') + }) + + test('shows empty state when no bulk operations found', async () => { + vi.mocked(adminRequestDoc).mockResolvedValue(mockBulkOperationsList([])) + + const output = mockAndCaptureOutput() + await listBulkOperations({storeFqdn, remoteApp}) + + 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 dc2f50d661c..e8dcc09a9d8 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 @@ -5,12 +5,18 @@ import { GetBulkOperationByIdQuery, } from '../../api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js' import {OrganizationApp} from '../../models/organization.js' -import {renderInfo, renderSuccess, renderError} from '@shopify/cli-kit/node/ui' +import { + ListBulkOperations, + ListBulkOperationsQuery, + ListBulkOperationsQueryVariables, +} from '../../api/graphql/bulk-operations/generated/list-bulk-operations.js' +import {renderInfo, renderSuccess, renderError, renderTable} from '@shopify/cli-kit/node/ui' import {outputContent, outputToken} from '@shopify/cli-kit/node/output' import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin' -import {timeAgo} from '@shopify/cli-kit/common/string' +import {timeAgo, formatDate} from '@shopify/cli-kit/common/string' import {BugError} from '@shopify/cli-kit/node/error' +import colors from '@shopify/cli-kit/node/colors' const API_VERSION = '2026-01' @@ -20,6 +26,11 @@ interface GetBulkOperationStatusOptions { remoteApp: OrganizationApp } +interface ListBulkOperationsOptions { + storeFqdn: string + remoteApp: OrganizationApp +} + export async function getBulkOperationStatus(options: GetBulkOperationStatusOptions): Promise { const {storeFqdn, operationId, remoteApp} = options @@ -45,6 +56,55 @@ export async function getBulkOperationStatus(options: GetBulkOperationStatusOpti } } +export async function listBulkOperations(options: ListBulkOperationsOptions): Promise { + const {storeFqdn, remoteApp} = options + + const appSecret = remoteApp.apiSecretKeys[0]?.secret + if (!appSecret) throw new BugError('No API secret keys found for app') + + const adminSession = await ensureAuthenticatedAdminAsApp(storeFqdn, remoteApp.apiKey, appSecret) + + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + + const response = await adminRequestDoc({ + query: ListBulkOperations, + session: adminSession, + variables: { + query: `created_at:>=${sevenDaysAgo}`, + first: 100, + sortKey: 'CREATED_AT', + reverse: true, + }, + version: API_VERSION, + }) + + const operations = response.bulkOperations.nodes.map((operation) => ({ + id: operation.id, + status: formatStatus(operation.status), + count: formatCount(operation.objectCount as number), + dateCreated: formatDate(new Date(String(operation.createdAt))), + dateFinished: operation.completedAt ? formatDate(new Date(String(operation.completedAt))) : '', + results: downloadLink(operation.url ?? operation.partialDataUrl), + })) + + if (operations.length === 0) { + renderInfo({body: 'no bulk operations found in the last 7 days'}) + return + } + + renderTable({ + rows: operations, + columns: { + id: {header: 'ID', color: 'yellow'}, + status: {header: 'STATUS'}, + count: {header: 'COUNT'}, + dateCreated: {header: 'DATE CREATED', color: 'cyan'}, + dateFinished: {header: 'DATE FINISHED', color: 'cyan'}, + results: {header: 'RESULTS'}, + }, + }) +} + function renderBulkOperationStatus(operation: BulkOperation): void { const {id, status, createdAt, completedAt, url, partialDataUrl} = operation const statusDescription = formatBulkOperationStatus(operation).value @@ -71,3 +131,19 @@ function formatTimeDifference(createdAt: unknown, completedAt?: unknown): string return `Started ${timeAgo(new Date(String(createdAt)), now)}` } } + +function formatStatus(status: string): string { + if (status === 'COMPLETED') return colors.green(status) + if (status === 'FAILED') return colors.red(status) + return colors.dim(status) +} + +function formatCount(count: number): string { + if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M` + if (count >= 1000) return `${(count / 1000).toFixed(1)}K` + return String(count) +} + +function downloadLink(downloadUrl: string | null | undefined): string { + return downloadUrl ? outputContent`${outputToken.link('download', downloadUrl)}`.value : '' +} diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 43f11d1a6fa..07611f0720e 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -92,7 +92,7 @@ "args": { }, "customPluginName": "@shopify/app", - "description": "Check the status of a bulk operation by ID.", + "description": "Check the status of a specific bulk operation by ID, or list all recent bulk operations.", "flags": { "client-id": { "description": "The Client ID of your app.", @@ -117,12 +117,11 @@ "type": "option" }, "id": { - "description": "The bulk operation ID.", + "description": "The bulk operation ID. If not provided, lists all recent bulk operations.", "env": "SHOPIFY_FLAG_ID", "hasDynamicHelp": false, "multiple": false, "name": "id", - "required": true, "type": "option" }, "no-color": { @@ -180,7 +179,7 @@ "pluginName": "@shopify/cli", "pluginType": "core", "strict": true, - "summary": "Check the status of a bulk operation." + "summary": "Check the status of bulk operations." }, "app:config:link": { "aliases": [ From 9a542997b2a38bba22fc995162634b91a2c91799 Mon Sep 17 00:00:00 2001 From: Jordan Verasamy Date: Tue, 2 Dec 2025 10:57:22 -0800 Subject: [PATCH 2/3] [PR feedback] add newlines before and after output of `bulk status` --- .../bulk-operations/bulk-operation-status.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) 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 e8dcc09a9d8..555f1a3dbcb 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 @@ -11,7 +11,7 @@ import { ListBulkOperationsQueryVariables, } from '../../api/graphql/bulk-operations/generated/list-bulk-operations.js' import {renderInfo, renderSuccess, renderError, renderTable} from '@shopify/cli-kit/node/ui' -import {outputContent, outputToken} from '@shopify/cli-kit/node/output' +import {outputContent, outputToken, outputNewline} from '@shopify/cli-kit/node/output' import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin' import {timeAgo, formatDate} from '@shopify/cli-kit/common/string' @@ -87,22 +87,25 @@ export async function listBulkOperations(options: ListBulkOperationsOptions): Pr results: downloadLink(operation.url ?? operation.partialDataUrl), })) + outputNewline() + if (operations.length === 0) { renderInfo({body: 'no bulk operations found in the last 7 days'}) - return + } else { + renderTable({ + rows: operations, + columns: { + id: {header: 'ID', color: 'yellow'}, + status: {header: 'STATUS'}, + count: {header: 'COUNT'}, + dateCreated: {header: 'DATE CREATED', color: 'cyan'}, + dateFinished: {header: 'DATE FINISHED', color: 'cyan'}, + results: {header: 'RESULTS'}, + }, + }) } - renderTable({ - rows: operations, - columns: { - id: {header: 'ID', color: 'yellow'}, - status: {header: 'STATUS'}, - count: {header: 'COUNT'}, - dateCreated: {header: 'DATE CREATED', color: 'cyan'}, - dateFinished: {header: 'DATE FINISHED', color: 'cyan'}, - results: {header: 'RESULTS'}, - }, - }) + outputNewline() } function renderBulkOperationStatus(operation: BulkOperation): void { From be161982b72e52b95a7af8ac7bf91d543f77dcb3 Mon Sep 17 00:00:00 2001 From: Jordan Verasamy Date: Tue, 2 Dec 2025 10:58:02 -0800 Subject: [PATCH 3/3] [PR feedback] Make descriptions of `bulk status` more specific Co-authored-by: Gonzalo Riestra --- packages/app/src/cli/commands/app/bulk/status.ts | 5 +++-- packages/cli/oclif.manifest.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/app/src/cli/commands/app/bulk/status.ts b/packages/app/src/cli/commands/app/bulk/status.ts index c9a022d43e9..710cc524ca3 100644 --- a/packages/app/src/cli/commands/app/bulk/status.ts +++ b/packages/app/src/cli/commands/app/bulk/status.ts @@ -10,7 +10,8 @@ import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn' export default class BulkStatus extends AppLinkedCommand { static summary = 'Check the status of bulk operations.' - static description = 'Check the status of a specific bulk operation by ID, or list all recent bulk operations.' + static description = + 'Check the status of a specific bulk operation by ID, or list all bulk operations in the last 7 days.' static hidden = true @@ -18,7 +19,7 @@ export default class BulkStatus extends AppLinkedCommand { ...globalFlags, ...appFlags, id: Flags.string({ - description: 'The bulk operation ID. If not provided, lists all recent bulk operations.', + description: 'The bulk operation ID. If not provided, lists all bulk operations in the last 7 days.', env: 'SHOPIFY_FLAG_ID', }), store: Flags.string({ diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 07611f0720e..3d058035145 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -92,7 +92,7 @@ "args": { }, "customPluginName": "@shopify/app", - "description": "Check the status of a specific bulk operation by ID, or list all recent bulk operations.", + "description": "Check the status of a specific bulk operation by ID, or list all bulk operations in the last 7 days.", "flags": { "client-id": { "description": "The Client ID of your app.", @@ -117,7 +117,7 @@ "type": "option" }, "id": { - "description": "The bulk operation ID. If not provided, lists all recent bulk operations.", + "description": "The bulk operation ID. If not provided, lists all bulk operations in the last 7 days.", "env": "SHOPIFY_FLAG_ID", "hasDynamicHelp": false, "multiple": false,