From efbdee409f700c1191cba161af3b78e75e813d79 Mon Sep 17 00:00:00 2001 From: Rod Mcnew Date: Mon, 14 Sep 2026 17:56:05 -0500 Subject: [PATCH 1/3] Add a local-only app logs query prototype Co-authored by AI (GPT-5) --- .../src/cli/commands/app/app-logs/query.ts | 58 +++++++++++ packages/app/src/cli/index.ts | 2 + .../src/cli/services/app-logs/query.test.ts | 88 +++++++++++++++++ .../app/src/cli/services/app-logs/query.ts | 73 ++++++++++++++ packages/cli/oclif.manifest.json | 95 ++++++++++++++++++- packages/cli/src/command-registry.ts | 1 + 6 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/cli/commands/app/app-logs/query.ts create mode 100644 packages/app/src/cli/services/app-logs/query.test.ts create mode 100644 packages/app/src/cli/services/app-logs/query.ts diff --git a/packages/app/src/cli/commands/app/app-logs/query.ts b/packages/app/src/cli/commands/app/app-logs/query.ts new file mode 100644 index 00000000000..53f4e96fe1a --- /dev/null +++ b/packages/app/src/cli/commands/app/app-logs/query.ts @@ -0,0 +1,58 @@ +import {queryAppLogs} from '../../../services/app-logs/query.js' +import BaseCommand from '@shopify/cli-kit/node/base-command' +import {globalFlags} from '@shopify/cli-kit/node/cli' +import {outputResult} from '@shopify/cli-kit/node/output' +import {Flags} from '@oclif/core' + +export default class Query extends BaseCommand { + static hidden = true + static summary = 'Prototype only: query app log summaries through the local App Management API.' + + static flags = { + ...globalFlags, + 'organization-id': Flags.string({ + required: true, + env: 'SHOPIFY_FLAG_ORGANIZATION_ID', + description: 'Local Business Platform organization ID.', + }), + 'client-id': Flags.string({required: true, env: 'SHOPIFY_FLAG_CLIENT_ID', description: 'App API key.'}), + minutes: Flags.integer({ + default: 15, + min: 1, + max: 60, + env: 'SHOPIFY_FLAG_MINUTES', + description: 'Query the last N minutes.', + }), + limit: Flags.integer({ + default: 10, + min: 1, + max: 100, + env: 'SHOPIFY_FLAG_LIMIT', + description: 'Maximum summary events to return.', + }), + type: Flags.string({ + multiple: true, + env: 'SHOPIFY_FLAG_TYPE', + options: ['WEBHOOK_DELIVERY', 'GRAPHQL_REQUEST', 'REST_REQUEST', 'FUNCTION_RUN'], + description: 'Restrict results to these event types.', + }), + demo: Flags.boolean({ + default: false, + env: 'SHOPIFY_FLAG_DEMO', + description: 'Use the loopback demo with seeded auth, not Identity login.', + }), + } + + public async run(): Promise { + const {flags} = await this.parse(Query) + const result = await queryAppLogs({ + organizationId: flags['organization-id'], + clientId: flags['client-id'], + minutes: flags.minutes, + limit: flags.limit, + types: flags.type, + demo: flags.demo, + }) + outputResult(JSON.stringify(result, null, 2)) + } +} diff --git a/packages/app/src/cli/index.ts b/packages/app/src/cli/index.ts index a6289bba107..bec72cb4dae 100644 --- a/packages/app/src/cli/index.ts +++ b/packages/app/src/cli/index.ts @@ -12,6 +12,7 @@ import DoctorSubmit from './commands/app/doctor/submit.js' import Doctor from './commands/app/doctor.js' import Logs from './commands/app/logs.js' import Sources from './commands/app/app-logs/sources.js' +import QueryLogs from './commands/app/app-logs/query.js' import EnvPull from './commands/app/env/pull.js' import EnvShow from './commands/app/env/show.js' import BulkExecute from './commands/app/bulk/execute.js' @@ -61,6 +62,7 @@ export const commands: {[key: string]: typeof AppLinkedCommand | typeof AppUnlin 'app:doctor': Doctor, 'app:logs': Logs, 'app:logs:sources': Sources, + 'app:logs:query': QueryLogs, 'app:import-custom-data-definitions': ImportCustomDataDefinitions, 'app:import-extensions': ImportExtensions, 'app:info': AppInfo, diff --git a/packages/app/src/cli/services/app-logs/query.test.ts b/packages/app/src/cli/services/app-logs/query.test.ts new file mode 100644 index 00000000000..052f1ac7697 --- /dev/null +++ b/packages/app/src/cli/services/app-logs/query.test.ts @@ -0,0 +1,88 @@ +import {queryAppLogs} from './query.js' +import {appManagementFqdn} from '@shopify/cli-kit/node/context/fqdn' +import {fetch} from '@shopify/cli-kit/node/http' +import {ensureAuthenticatedAppManagementAndBusinessPlatform} from '@shopify/cli-kit/node/session' +import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' +import {afterEach, beforeEach, expect, test, vi} from 'vitest' +import {Response} from 'node-fetch' + +vi.mock('@shopify/cli-kit/node/context/fqdn') +vi.mock('@shopify/cli-kit/node/http') +vi.mock('@shopify/cli-kit/node/session') + +const options = {organizationId: '1', clientId: 'test-app', minutes: 15, limit: 3, demo: false} + +beforeEach(() => { + vi.stubEnv('SHOPIFY_APP_LOG_QUERY_PROTOTYPE', '1') + vi.stubEnv('SHOPIFY_SERVICE_ENV', 'local') + vi.mocked(appManagementFqdn).mockResolvedValue('app.shop.dev') + vi.mocked(ensureAuthenticatedAppManagementAndBusinessPlatform).mockResolvedValue({ + appManagementToken: 'atkn_local-identity', + userId: 'local-user', + businessPlatformToken: 'unused', + }) +}) + +afterEach(() => { + vi.unstubAllEnvs() +}) + +test('refuses production services before authenticating or sending a request', async () => { + vi.stubEnv('SHOPIFY_SERVICE_ENV', 'production') + await expect(queryAppLogs(options)).rejects.toThrow('Prototype only') + expect(ensureAuthenticatedAppManagementAndBusinessPlatform).not.toHaveBeenCalled() + expect(fetch).not.toHaveBeenCalled() +}) + +test('refuses an unexpected host before obtaining a token', async () => { + vi.mocked(appManagementFqdn).mockResolvedValue('app.shopify.com') + await expect(queryAppLogs(options)).rejects.toThrow('only call app.shop.dev') + expect(ensureAuthenticatedAppManagementAndBusinessPlatform).not.toHaveBeenCalled() +}) + +test('sends a bounded app query using the normal CLI authentication helper', async () => { + const result = {app_key: 'test-app', events: [{'payload.type': 'WEBHOOK_DELIVERY'}], exhaustive: false} + vi.mocked(fetch).mockResolvedValue(new Response(JSON.stringify(result), {status: 200})) + await expect(queryAppLogs({...options, types: ['WEBHOOK_DELIVERY']})).resolves.toEqual(result) + expect(fetch).toHaveBeenCalledWith( + 'https://app.shop.dev/app_management/unstable/organizations/1/app_logs/query', + expect.objectContaining({ + method: 'POST', + redirect: 'error', + size: 1024 * 1024, + headers: expect.objectContaining({authorization: 'Bearer atkn_local-identity'}), + body: expect.stringContaining('"types":["WEBHOOK_DELIVERY"]'), + }), + ) + const input = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) + expect(input.api_key).toBe('test-app') + expect(input.limit).toBe(3) + expect(Date.parse(input.end_time) - Date.parse(input.start_time)).toBe(15 * 60 * 1000) +}) + +test('uses the temporary token only for the fixed loopback demo, without logging into Identity', async () => { + await inTemporaryDirectory(async (directory) => { + const path = joinPath(directory, 'token') + await writeFile(path, 'atkn_demo-only') + vi.stubEnv('APP_LOG_QUERY_DEMO_TOKEN_FILE', path) + vi.mocked(fetch).mockResolvedValue(new Response('{"events":[]}', {status: 200})) + await expect(queryAppLogs({...options, demo: true})).resolves.toEqual({events: []}) + expect(fetch).toHaveBeenCalledWith( + 'http://127.0.0.1:4387/app_management/unstable/organizations/1/app_logs/query', + expect.objectContaining({headers: expect.objectContaining({authorization: 'Bearer atkn_demo-only'})}), + ) + expect(ensureAuthenticatedAppManagementAndBusinessPlatform).not.toHaveBeenCalled() + }) +}) + +test('surfaces API errors instead of turning them into an empty result', async () => { + vi.mocked(fetch).mockResolvedValue(new Response('private upstream details', {status: 502})) + await expect(queryAppLogs(options)).rejects.toThrow('HTTP 502') +}) + +test('rejects a path-injection organization ID and oversized request limits', async () => { + await expect(queryAppLogs({...options, organizationId: '../2'})).rejects.toThrow('numeric organization ID') + await expect(queryAppLogs({...options, limit: 101})).rejects.toThrow('limit of 1–100') + expect(fetch).not.toHaveBeenCalled() +}) diff --git a/packages/app/src/cli/services/app-logs/query.ts b/packages/app/src/cli/services/app-logs/query.ts new file mode 100644 index 00000000000..d4ac61a5a1f --- /dev/null +++ b/packages/app/src/cli/services/app-logs/query.ts @@ -0,0 +1,73 @@ +import {appManagementHeaders} from '@shopify/cli-kit/node/api/app-management' +import {appManagementFqdn} from '@shopify/cli-kit/node/context/fqdn' +import {AbortError} from '@shopify/cli-kit/node/error' +import {readFile} from '@shopify/cli-kit/node/fs' +import {fetch} from '@shopify/cli-kit/node/http' +import {ensureAuthenticatedAppManagementAndBusinessPlatform} from '@shopify/cli-kit/node/session' + +interface QueryOptions { + organizationId: string + clientId: string + minutes: number + limit: number + types?: string[] + demo: boolean +} + +export async function queryAppLogs(options: QueryOptions): Promise { + if (process.env.SHOPIFY_APP_LOG_QUERY_PROTOTYPE !== '1' || process.env.SHOPIFY_SERVICE_ENV !== 'local') { + throw new AbortError('Prototype only: set SHOPIFY_APP_LOG_QUERY_PROTOTYPE=1 and SHOPIFY_SERVICE_ENV=local.') + } + if (!/^\d+$/.test(options.organizationId) || !options.clientId) { + throw new AbortError('Provide a numeric organization ID and a nonempty app client ID.') + } + if ( + !Number.isInteger(options.minutes) || + options.minutes < 1 || + options.minutes > 60 || + !Number.isInteger(options.limit) || + options.limit < 1 || + options.limit > 100 + ) { + throw new AbortError('Use 1–60 minutes and a limit of 1–100 events.') + } + + const {origin, token} = await queryConnection(options.demo) + const end = new Date() + const response = await fetch( + `${origin}/app_management/unstable/organizations/${options.organizationId}/app_logs/query`, + { + method: 'POST', + redirect: 'error', + signal: AbortSignal.timeout(15000), + size: 1024 * 1024, + headers: appManagementHeaders(token), + body: JSON.stringify({ + api_key: options.clientId, + start_time: new Date(end.getTime() - options.minutes * 60 * 1000).toISOString(), + end_time: end.toISOString(), + limit: options.limit, + ...(options.types ? {types: options.types} : {}), + }), + }, + ) + if (!response.ok) { + throw new AbortError(`Local log query failed (HTTP ${response.status}). Check the local API server output.`) + } + return response.json() +} + +async function queryConnection(demo: boolean): Promise<{origin: string; token: string}> { + if (demo) { + const path = process.env.APP_LOG_QUERY_DEMO_TOKEN_FILE + if (!path) throw new AbortError('Set APP_LOG_QUERY_DEMO_TOKEN_FILE to the file printed by the local demo server.') + const token = (await readFile(path)).trim() + if (!token.startsWith('atkn_') || token.length > 4096) throw new AbortError('Invalid demo token file.') + return {origin: 'http://127.0.0.1:4387', token} + } + + const host = await appManagementFqdn() + if (host !== 'app.shop.dev') throw new AbortError('This prototype can only call app.shop.dev.') + const {appManagementToken} = await ensureAuthenticatedAppManagementAndBusinessPlatform() + return {origin: `https://${host}`, token: appManagementToken} +} diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index e643cbb6194..7d63c233645 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3313,6 +3313,99 @@ "strict": true, "summary": "Stream detailed logs for your Shopify app." }, + "app:logs:query": { + "aliases": [ + ], + "args": { + }, + "customPluginName": "@shopify/app", + "enableJsonFlag": false, + "flags": { + "client-id": { + "description": "App API key.", + "env": "SHOPIFY_FLAG_CLIENT_ID", + "hasDynamicHelp": false, + "multiple": false, + "name": "client-id", + "required": true, + "type": "option" + }, + "demo": { + "allowNo": false, + "description": "Use the loopback demo with seeded auth, not Identity login.", + "env": "SHOPIFY_FLAG_DEMO", + "name": "demo", + "type": "boolean" + }, + "limit": { + "default": 10, + "description": "Maximum summary events to return.", + "env": "SHOPIFY_FLAG_LIMIT", + "hasDynamicHelp": false, + "multiple": false, + "name": "limit", + "type": "option" + }, + "minutes": { + "default": 15, + "description": "Query the last N minutes.", + "env": "SHOPIFY_FLAG_MINUTES", + "hasDynamicHelp": false, + "multiple": false, + "name": "minutes", + "type": "option" + }, + "no-color": { + "allowNo": false, + "description": "Disable color output.", + "env": "SHOPIFY_FLAG_NO_COLOR", + "hidden": false, + "name": "no-color", + "type": "boolean" + }, + "organization-id": { + "description": "Local Business Platform organization ID.", + "env": "SHOPIFY_FLAG_ORGANIZATION_ID", + "hasDynamicHelp": false, + "multiple": false, + "name": "organization-id", + "required": true, + "type": "option" + }, + "type": { + "description": "Restrict results to these event types.", + "env": "SHOPIFY_FLAG_TYPE", + "hasDynamicHelp": false, + "multiple": true, + "name": "type", + "options": [ + "WEBHOOK_DELIVERY", + "GRAPHQL_REQUEST", + "REST_REQUEST", + "FUNCTION_RUN" + ], + "type": "option" + }, + "verbose": { + "allowNo": false, + "description": "Increase the verbosity of the output. May include sensitive data.", + "env": "SHOPIFY_FLAG_VERBOSE", + "hidden": false, + "name": "verbose", + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hidden": true, + "hiddenAliases": [ + ], + "id": "app:logs:query", + "pluginAlias": "@shopify/cli", + "pluginName": "@shopify/cli", + "pluginType": "core", + "strict": true, + "summary": "Prototype only: query app log summaries through the local App Management API." + }, "app:logs:sources": { "aliases": [ ], @@ -10632,4 +10725,4 @@ } }, "version": "4.8.0" -} \ No newline at end of file +} diff --git a/packages/cli/src/command-registry.ts b/packages/cli/src/command-registry.ts index 2f8180831e6..e60bd7293d8 100644 --- a/packages/cli/src/command-registry.ts +++ b/packages/cli/src/command-registry.ts @@ -62,6 +62,7 @@ function resolvePackageDir(packageName: string): string { const entryPointOverrides: Record = { 'app:logs:sources': 'dist/cli/commands/app/app-logs/sources.js', + 'app:logs:query': 'dist/cli/commands/app/app-logs/query.js', 'demo:watcher': 'dist/cli/commands/app/demo/watcher.js', 'kitchen-sink': 'dist/cli/commands/kitchen-sink/index.js', 'doctor-release': 'dist/cli/commands/doctor-release/doctor-release.js', From 1207e729dd52cea2193e887910919c9d6fb7dadc Mon Sep 17 00:00:00 2001 From: Rod Mcnew Date: Mon, 14 Sep 2026 18:28:21 -0500 Subject: [PATCH 2/3] Fix app log query prototype packaging Co-authored by AI (GPT-5) --- packages/cli/bin/bundle.js | 1 + packages/cli/oclif.manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/bin/bundle.js b/packages/cli/bin/bundle.js index 537d650a5be..0f4854ab846 100644 --- a/packages/cli/bin/bundle.js +++ b/packages/cli/bin/bundle.js @@ -55,6 +55,7 @@ const hookEntryPoints = glob.sync('./src/hooks/*.ts', { const manifest = JSON.parse(readFileSync(joinPath(process.cwd(), 'oclif.manifest.json'), 'utf8')) const commandEntryPointOverrides = { 'app:logs:sources': 'cli/commands/app/app-logs/sources', + 'app:logs:query': 'cli/commands/app/app-logs/query', 'demo:watcher': 'cli/commands/app/demo/watcher', 'kitchen-sink': 'cli/commands/kitchen-sink/index', 'doctor-release': 'cli/commands/doctor-release/doctor-release', diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 7d63c233645..a5a3186a6a7 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -10725,4 +10725,4 @@ } }, "version": "4.8.0" -} +} \ No newline at end of file From 3acc3776459f1f52037d4d96f77d00f4f67c51a5 Mon Sep 17 00:00:00 2001 From: Rod Mcnew Date: Tue, 15 Sep 2026 11:07:44 -0500 Subject: [PATCH 3/3] Add row offsets to the log query prototype and leave limits to the API Co-authored by AI (GPT-5) Assisted-By: devx/77cd7a80-dbde-4d20-9b34-75ed3bac34bb --- .../src/cli/commands/app/app-logs/query.ts | 9 +++- .../src/cli/services/app-logs/query.test.ts | 42 +++++++++++++++++-- .../app/src/cli/services/app-logs/query.ts | 9 ++-- packages/cli/oclif.manifest.json | 9 ++++ 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/packages/app/src/cli/commands/app/app-logs/query.ts b/packages/app/src/cli/commands/app/app-logs/query.ts index 53f4e96fe1a..5c1b6bea295 100644 --- a/packages/app/src/cli/commands/app/app-logs/query.ts +++ b/packages/app/src/cli/commands/app/app-logs/query.ts @@ -19,17 +19,21 @@ export default class Query extends BaseCommand { minutes: Flags.integer({ default: 15, min: 1, - max: 60, env: 'SHOPIFY_FLAG_MINUTES', description: 'Query the last N minutes.', }), limit: Flags.integer({ default: 10, min: 1, - max: 100, env: 'SHOPIFY_FLAG_LIMIT', description: 'Maximum summary events to return.', }), + offset: Flags.integer({ + default: 0, + min: 0, + env: 'SHOPIFY_FLAG_OFFSET', + description: 'Number of matching rows to skip. Results are unordered; this is not a reliable export cursor.', + }), type: Flags.string({ multiple: true, env: 'SHOPIFY_FLAG_TYPE', @@ -50,6 +54,7 @@ export default class Query extends BaseCommand { clientId: flags['client-id'], minutes: flags.minutes, limit: flags.limit, + offset: flags.offset, types: flags.type, demo: flags.demo, }) diff --git a/packages/app/src/cli/services/app-logs/query.test.ts b/packages/app/src/cli/services/app-logs/query.test.ts index 052f1ac7697..489f075d066 100644 --- a/packages/app/src/cli/services/app-logs/query.test.ts +++ b/packages/app/src/cli/services/app-logs/query.test.ts @@ -11,7 +11,7 @@ vi.mock('@shopify/cli-kit/node/context/fqdn') vi.mock('@shopify/cli-kit/node/http') vi.mock('@shopify/cli-kit/node/session') -const options = {organizationId: '1', clientId: 'test-app', minutes: 15, limit: 3, demo: false} +const options = {organizationId: '1', clientId: 'test-app', minutes: 15, limit: 3, offset: 0, demo: false} beforeEach(() => { vi.stubEnv('SHOPIFY_APP_LOG_QUERY_PROTOTYPE', '1') @@ -50,7 +50,6 @@ test('sends a bounded app query using the normal CLI authentication helper', asy expect.objectContaining({ method: 'POST', redirect: 'error', - size: 1024 * 1024, headers: expect.objectContaining({authorization: 'Bearer atkn_local-identity'}), body: expect.stringContaining('"types":["WEBHOOK_DELIVERY"]'), }), @@ -58,6 +57,7 @@ test('sends a bounded app query using the normal CLI authentication helper', asy const input = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) expect(input.api_key).toBe('test-app') expect(input.limit).toBe(3) + expect(input.offset).toBe(0) expect(Date.parse(input.end_time) - Date.parse(input.start_time)).toBe(15 * 60 * 1000) }) @@ -81,8 +81,42 @@ test('surfaces API errors instead of turning them into an empty result', async ( await expect(queryAppLogs(options)).rejects.toThrow('HTTP 502') }) -test('rejects a path-injection organization ID and oversized request limits', async () => { +test('forwards a 10000 event limit and 1000000 offset independently', async () => { + vi.mocked(fetch).mockResolvedValue(new Response('{"events":[]}', {status: 200})) + await expect(queryAppLogs({...options, limit: 10_000, offset: 1_000_000})).resolves.toEqual({events: []}) + const input = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) + expect(input.limit).toBe(10_000) + expect(input.offset).toBe(1_000_000) +}) + +test('leaves upper policy limits to the server', async () => { + vi.mocked(fetch).mockResolvedValue(new Response('{"error":"Query limits exceeded"}', {status: 400})) + await expect(queryAppLogs({...options, minutes: 61, limit: 10_001, offset: 1_000_001})).rejects.toThrow('HTTP 400') + const input = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) + expect(input.limit).toBe(10_001) + expect(input.offset).toBe(1_000_001) +}) + +test('reads valid responses larger than one MiB without a client byte cap', async () => { + const result = {events: [{'payload.target': 'x'.repeat(2 * 1024 * 1024)}]} + vi.mocked(fetch).mockImplementation(async (_url, init) => { + const responseOptions = {status: 200, size: init?.size} + return new Response(JSON.stringify(result), responseOptions) + }) + + await expect(queryAppLogs(options)).resolves.toEqual(result) + expect(vi.mocked(fetch).mock.calls[0]![1]?.size).toBeUndefined() +}) + +test('rejects a path-injection organization ID', async () => { await expect(queryAppLogs({...options, organizationId: '../2'})).rejects.toThrow('numeric organization ID') - await expect(queryAppLogs({...options, limit: 101})).rejects.toThrow('limit of 1–100') expect(fetch).not.toHaveBeenCalled() }) + +test.each([{minutes: 0}, {minutes: 1.5}, {limit: 0}, {limit: 1.5}, {offset: -1}, {offset: 1.5}])( + 'rejects malformed numeric options %j before making a request', + async (invalidOptions) => { + await expect(queryAppLogs({...options, ...invalidOptions})).rejects.toThrow('positive integers') + expect(fetch).not.toHaveBeenCalled() + }, +) diff --git a/packages/app/src/cli/services/app-logs/query.ts b/packages/app/src/cli/services/app-logs/query.ts index d4ac61a5a1f..e7556b68d0d 100644 --- a/packages/app/src/cli/services/app-logs/query.ts +++ b/packages/app/src/cli/services/app-logs/query.ts @@ -10,6 +10,7 @@ interface QueryOptions { clientId: string minutes: number limit: number + offset: number types?: string[] demo: boolean } @@ -24,12 +25,12 @@ export async function queryAppLogs(options: QueryOptions): Promise { if ( !Number.isInteger(options.minutes) || options.minutes < 1 || - options.minutes > 60 || !Number.isInteger(options.limit) || options.limit < 1 || - options.limit > 100 + !Number.isInteger(options.offset) || + options.offset < 0 ) { - throw new AbortError('Use 1–60 minutes and a limit of 1–100 events.') + throw new AbortError('Use positive integers for minutes and limit, and a nonnegative integer for offset.') } const {origin, token} = await queryConnection(options.demo) @@ -40,13 +41,13 @@ export async function queryAppLogs(options: QueryOptions): Promise { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15000), - size: 1024 * 1024, headers: appManagementHeaders(token), body: JSON.stringify({ api_key: options.clientId, start_time: new Date(end.getTime() - options.minutes * 60 * 1000).toISOString(), end_time: end.toISOString(), limit: options.limit, + offset: options.offset, ...(options.types ? {types: options.types} : {}), }), }, diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index a5a3186a6a7..ab1800731b7 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3363,6 +3363,15 @@ "name": "no-color", "type": "boolean" }, + "offset": { + "default": 0, + "description": "Number of matching rows to skip. Results are unordered; this is not a reliable export cursor.", + "env": "SHOPIFY_FLAG_OFFSET", + "hasDynamicHelp": false, + "multiple": false, + "name": "offset", + "type": "option" + }, "organization-id": { "description": "Local Business Platform organization ID.", "env": "SHOPIFY_FLAG_ORGANIZATION_ID",