@@ -2,18 +2,22 @@ import {executeBulkOperation} from './execute-bulk-operation.js'
22import { runBulkOperationQuery } from './run-query.js'
33import { runBulkOperationMutation } from './run-mutation.js'
44import { watchBulkOperation } from './watch-bulk-operation.js'
5+ import { downloadBulkOperationResults } from './download-bulk-operation-results.js'
56import { AppLinkedInterface } from '../../models/app/app.js'
67import { BulkOperationRunQueryMutation } from '../../api/graphql/bulk-operations/generated/bulk-operation-run-query.js'
78import { BulkOperationRunMutationMutation } from '../../api/graphql/bulk-operations/generated/bulk-operation-run-mutation.js'
89import { renderSuccess , renderWarning , renderError } from '@shopify/cli-kit/node/ui'
910import { ensureAuthenticatedAdmin } from '@shopify/cli-kit/node/session'
11+ import { writeFile } from '@shopify/cli-kit/node/fs'
1012import { describe , test , expect , vi , beforeEach } from 'vitest'
1113
1214vi . mock ( './run-query.js' )
1315vi . mock ( './run-mutation.js' )
1416vi . mock ( './watch-bulk-operation.js' )
17+ vi . mock ( './download-bulk-operation-results.js' )
1518vi . mock ( '@shopify/cli-kit/node/ui' )
1619vi . mock ( '@shopify/cli-kit/node/session' )
20+ vi . mock ( '@shopify/cli-kit/node/fs' )
1721
1822describe ( 'executeBulkOperation' , ( ) => {
1923 const mockApp = {
@@ -193,6 +197,7 @@ describe('executeBulkOperation', () => {
193197
194198 vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
195199 vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
200+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( '{"id":"gid://shopify/Product/123"}' )
196201
197202 await executeBulkOperation ( {
198203 app : mockApp ,
@@ -205,11 +210,75 @@ describe('executeBulkOperation', () => {
205210 expect ( renderSuccess ) . toHaveBeenCalledWith (
206211 expect . objectContaining ( {
207212 headline : expect . stringContaining ( 'Bulk operation succeeded.' ) ,
208- body : expect . arrayContaining ( [ expect . stringContaining ( 'https://example.com/download' ) ] ) ,
209213 } ) ,
210214 )
211215 } )
212216
217+ test ( 'writes results to file when --output-file flag is provided' , async ( ) => {
218+ const query = '{ products { edges { node { id } } } }'
219+ const outputFile = '/tmp/results.jsonl'
220+ const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
221+
222+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
223+ bulkOperation : createdBulkOperation ,
224+ userErrors : [ ] ,
225+ }
226+ const completedOperation = {
227+ ...createdBulkOperation ,
228+ status : 'COMPLETED' as const ,
229+ url : 'https://example.com/download' ,
230+ objectCount : '2' ,
231+ }
232+
233+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
234+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
235+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsContent )
236+
237+ await executeBulkOperation ( {
238+ app : mockApp ,
239+ storeFqdn,
240+ query,
241+ watch : true ,
242+ outputFile,
243+ } )
244+
245+ expect ( writeFile ) . toHaveBeenCalledWith ( outputFile , resultsContent )
246+ } )
247+
248+ test ( 'writes results to stdout when --output-file flag is not provided' , async ( ) => {
249+ const query = '{ products { edges { node { id } } } }'
250+ const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
251+
252+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
253+ bulkOperation : createdBulkOperation ,
254+ userErrors : [ ] ,
255+ }
256+ const completedOperation = {
257+ ...createdBulkOperation ,
258+ status : 'COMPLETED' as const ,
259+ url : 'https://example.com/download' ,
260+ objectCount : '2' ,
261+ }
262+
263+ const stdoutWriteSpy = vi . spyOn ( process . stdout , 'write' ) . mockImplementation ( ( ) => true )
264+
265+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
266+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
267+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsContent )
268+
269+ await executeBulkOperation ( {
270+ app : mockApp ,
271+ storeFqdn,
272+ query,
273+ watch : true ,
274+ } )
275+
276+ expect ( stdoutWriteSpy ) . toHaveBeenCalledWith ( resultsContent )
277+ expect ( writeFile ) . not . toHaveBeenCalled ( )
278+
279+ stdoutWriteSpy . mockRestore ( )
280+ } )
281+
213282 test . each ( [ 'FAILED' , 'CANCELED' , 'EXPIRED' ] as const ) (
214283 'waits for operation to finish and renders error when watch is provided and operation finishes with %s status' ,
215284 async ( status ) => {
0 commit comments