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 4fd45eb8318..024de4d7d71 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/commands/app/execute.ts b/packages/app/src/cli/commands/app/execute.ts index 72a2b1bd6c2..b8d081e673e 100644 --- a/packages/app/src/cli/commands/app/execute.ts +++ b/packages/app/src/cli/commands/app/execute.ts @@ -35,7 +35,7 @@ export default class Execute extends AppLinkedCommand { }) await executeBulkOperation({ - app: appContextResult.app, + remoteApp: appContextResult.remoteApp, storeFqdn: store.shopDomain, query: flags.query, variables: flags.variables, 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 2ded5b84ad3..5eef4e3e6fe 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 @@ -3,11 +3,11 @@ import {runBulkOperationQuery} from './run-query.js' import {runBulkOperationMutation} from './run-mutation.js' import {watchBulkOperation} from './watch-bulk-operation.js' import {downloadBulkOperationResults} from './download-bulk-operation-results.js' -import {AppLinkedInterface} from '../../models/app/app.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 {renderSuccess, renderWarning, renderError} from '@shopify/cli-kit/node/ui' -import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session' +import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs' import {joinPath} from '@shopify/cli-kit/node/path' import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' @@ -18,13 +18,21 @@ vi.mock('./run-mutation.js') vi.mock('./watch-bulk-operation.js') vi.mock('./download-bulk-operation-results.js') vi.mock('@shopify/cli-kit/node/ui') -vi.mock('@shopify/cli-kit/node/session') vi.mock('@shopify/cli-kit/node/fs') +vi.mock('@shopify/cli-kit/node/session', async () => { + const actual = await vi.importActual('@shopify/cli-kit/node/session') + return { + ...actual, + ensureAuthenticatedAdminAsApp: vi.fn(), + } +}) describe('executeBulkOperation', () => { - const mockApp = { - name: 'Test App', - } as AppLinkedInterface + const mockRemoteApp = { + apiKey: 'test-app-client-id', + apiSecretKeys: [{secret: 'test-api-secret'}], + title: 'Test App', + } as OrganizationApp const storeFqdn = 'test-store.myshopify.com' const mockAdminSession = {token: 'test-token', storeFqdn} @@ -43,7 +51,7 @@ describe('executeBulkOperation', () => { } beforeEach(() => { - vi.mocked(ensureAuthenticatedAdmin).mockResolvedValue(mockAdminSession) + vi.mocked(ensureAuthenticatedAdminAsApp).mockResolvedValue(mockAdminSession) }) afterEach(() => { @@ -59,7 +67,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, }) @@ -80,7 +88,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, }) @@ -101,7 +109,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: mutation, }) @@ -124,7 +132,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: mutation, variables, @@ -145,7 +153,7 @@ describe('executeBulkOperation', () => { } vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, }) @@ -169,7 +177,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationQuery).mockResolvedValue(mockResponse) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, }) @@ -187,7 +195,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: malformedQuery, }), @@ -203,7 +211,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: multipleOperations, }), @@ -223,7 +231,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: noOperations, }), @@ -251,7 +259,7 @@ describe('executeBulkOperation', () => { vi.mocked(runBulkOperationMutation).mockResolvedValue(mockResponse as any) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: mutation, variableFile: variableFilePath, @@ -273,7 +281,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query: mutation, variableFile: nonExistentPath, @@ -291,7 +299,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, variables, @@ -311,7 +319,7 @@ describe('executeBulkOperation', () => { await expect( executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, variableFile: variableFilePath, @@ -341,7 +349,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue('{"id":"gid://shopify/Product/123"}') await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, watch: true, @@ -376,7 +384,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue(resultsContent) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, watch: true, @@ -408,7 +416,7 @@ describe('executeBulkOperation', () => { vi.mocked(downloadBulkOperationResults).mockResolvedValue(resultsContent) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, watch: true, @@ -436,7 +444,7 @@ describe('executeBulkOperation', () => { vi.mocked(watchBulkOperation).mockResolvedValue(finishedOperation) await executeBulkOperation({ - app: mockApp, + remoteApp: mockRemoteApp, storeFqdn, query, watch: true, 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 6ffe33a1615..f68709c93ee 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,16 +3,16 @@ 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 {AppLinkedInterface} from '../../models/app/app.js' +import {OrganizationApp} from '../../models/organization.js' import {renderSuccess, renderInfo, renderError, renderWarning} from '@shopify/cli-kit/node/ui' import {outputContent, outputToken, outputResult} from '@shopify/cli-kit/node/output' -import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session' -import {AbortError} from '@shopify/cli-kit/node/error' +import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session' +import {AbortError, BugError} from '@shopify/cli-kit/node/error' import {parse} from 'graphql' import {readFile, writeFile, fileExists} from '@shopify/cli-kit/node/fs' interface ExecuteBulkOperationInput { - app: AppLinkedInterface + remoteApp: OrganizationApp storeFqdn: string query: string variables?: string[] @@ -39,13 +39,17 @@ async function parseVariablesToJsonl(variables?: string[], variableFile?: string } export async function executeBulkOperation(input: ExecuteBulkOperationInput): Promise { - const {app, storeFqdn, query, variables, variableFile, outputFile, watch = false} = input + const {remoteApp, storeFqdn, query, variables, variableFile, outputFile, watch = false} = input renderInfo({ headline: 'Starting bulk operation.', - body: `App: ${app.name}\nStore: ${storeFqdn}`, + body: `App: ${remoteApp.title}\nStore: ${storeFqdn}`, }) - const adminSession = await ensureAuthenticatedAdmin(storeFqdn) + + 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 variablesJsonl = await parseVariablesToJsonl(variables, variableFile)