From e692f523714c6d2997737092d03775dcd3f47211 Mon Sep 17 00:00:00 2001 From: szdziedzic Date: Thu, 2 Jul 2026 13:13:20 +0200 Subject: [PATCH 1/3] [eas-cli] Retain agent-device session artifacts --- .../src/steps/functions/startAgentDeviceRemoteSession.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts b/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts index 2ac35c6c00..d1c5d84430 100644 --- a/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts +++ b/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts @@ -32,6 +32,10 @@ const AGENT_DEVICE_REPO_URL = 'https://github.com/callstackincubator/agent-devic const SRC_DIR = '/tmp/agent-device-src'; const DAEMON_JSON_PATH = path.join(os.homedir(), '.agent-device', 'daemon.json'); const STARTUP_TIMEOUT_MS = 60_000; +const AGENT_DEVICE_DAEMON_ENV = { + AGENT_DEVICE_DAEMON_SERVER_MODE: 'http', + AGENT_DEVICE_RETAIN_ARTIFACTS: '1', +}; export function createStartAgentDeviceRemoteSessionBuildFunction( ctx: CustomBuildContext @@ -144,7 +148,7 @@ async function startAgentDeviceDaemonAsync({ return spawnDetached({ command: 'node', args: [daemonPath], - env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' }, + env: { ...env, ...AGENT_DEVICE_DAEMON_ENV }, }); } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); @@ -201,7 +205,7 @@ async function startAgentDeviceDaemonFromGitAsync({ command: 'bun', args: ['run', 'src/daemon.ts'], cwd: SRC_DIR, - env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' }, + env: { ...env, ...AGENT_DEVICE_DAEMON_ENV }, }); } From 4a563884d9e7f0ccf82f967451ffa9fefa954da1 Mon Sep 17 00:00:00 2001 From: szdziedzic Date: Thu, 2 Jul 2026 20:52:03 +0200 Subject: [PATCH 2/3] [eas-cli] Upload agent-device session artifacts --- .../startAgentDeviceRemoteSession.ts | 9 +- .../__tests__/agentDeviceArtifacts.test.ts | 195 +++++++++++++++++ .../src/steps/utils/agentDeviceArtifacts.ts | 201 ++++++++++++++++++ 3 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts create mode 100644 packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts diff --git a/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts b/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts index d1c5d84430..7825cb9470 100644 --- a/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts +++ b/packages/build-tools/src/steps/functions/startAgentDeviceRemoteSession.ts @@ -14,6 +14,7 @@ import path from 'node:path'; import { type CustomBuildContext } from '../../customBuildContext'; import { Sentry } from '../../sentry'; +import { pollAgentDeviceArtifactsForUploadAsync } from '../utils/agentDeviceArtifacts'; import { type DetachedProcessHandle, getDeviceRunSessionIdOrThrow, @@ -28,7 +29,7 @@ import { } from '../utils/remoteDeviceRunSession'; const AGENT_DEVICE_PACKAGE_NAME = 'agent-device'; -const AGENT_DEVICE_REPO_URL = 'https://github.com/callstackincubator/agent-device.git'; +const AGENT_DEVICE_REPO_URL = 'https://github.com/callstack/agent-device.git'; const SRC_DIR = '/tmp/agent-device-src'; const DAEMON_JSON_PATH = path.join(os.homedir(), '.agent-device', 'daemon.json'); const STARTUP_TIMEOUT_MS = 60_000; @@ -113,6 +114,12 @@ export function createStartAgentDeviceRemoteSessionBuildFunction( }, logger, }); + void pollAgentDeviceArtifactsForUploadAsync(ctx, { + deviceRunSessionId, + daemonUrl: `http://127.0.0.1:${daemonPort}`, + daemonToken, + logger, + }); logger.info('Remote session is live. Keeping the job alive until the session is stopped.'); // Keep the turtle job alive so the daemon and tunnel stay reachable diff --git a/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts new file mode 100644 index 0000000000..c90f058476 --- /dev/null +++ b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts @@ -0,0 +1,195 @@ +import { bunyan } from '@expo/logger'; +import fetch from 'node-fetch'; +import { Readable } from 'node:stream'; + +import { CustomBuildContext } from '../../../customBuildContext'; +import { uploadDeviceRunSessionArtifactAsync } from '../deviceRunSessionArtifacts'; +import { + listAgentDeviceArtifactsAsync, + pollAgentDeviceArtifactsForUploadAsync, + uploadAgentDeviceArtifactAsync, +} from '../agentDeviceArtifacts'; + +jest.mock('../deviceRunSessionArtifacts'); +jest.mock('node-fetch'); + +const { Response } = jest.requireActual('node-fetch') as typeof import('node-fetch'); + +async function readStreamAsync(stream: NodeJS.ReadableStream): Promise { + for await (const chunk of stream as Readable) { + void chunk; + } +} + +async function flushPromisesAsync(): Promise { + for (let i = 0; i < 10; i++) { + await jest.advanceTimersByTimeAsync(0); + await Promise.resolve(); + } +} + +function createLoggerMock(): bunyan { + return { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + } as unknown as bunyan; +} + +describe(listAgentDeviceArtifactsAsync, () => { + beforeEach(() => { + jest.mocked(fetch).mockReset(); + }); + + it('lists agent-device artifacts with bearer auth', async () => { + jest.mocked(fetch).mockResolvedValue( + new Response( + JSON.stringify({ + artifacts: [ + { + id: 'artifact-id', + filename: 'report.json', + mimeType: 'application/json', + sizeBytes: 123, + createdAt: '2026-07-02T12:00:00.000Z', + expiresAt: '2026-07-02T12:15:00.000Z', + }, + ], + }) + ) + ); + + const artifacts = await listAgentDeviceArtifactsAsync({ + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + }); + + expect(artifacts).toEqual([ + { + id: 'artifact-id', + filename: 'report.json', + mimeType: 'application/json', + sizeBytes: 123, + createdAt: '2026-07-02T12:00:00.000Z', + expiresAt: '2026-07-02T12:15:00.000Z', + }, + ]); + expect(jest.mocked(fetch)).toHaveBeenCalledWith('http://127.0.0.1:1234/artifacts', { + headers: { Authorization: 'Bearer daemon-token' }, + }); + }); + + it('reports artifact inventory as unsupported when the daemon does not expose the endpoint', async () => { + jest.mocked(fetch).mockResolvedValue(new Response('Not found', { status: 404 })); + + await expect( + listAgentDeviceArtifactsAsync({ + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + }) + ).rejects.toThrow('agent-device daemon does not expose artifact inventory.'); + }); +}); + +describe(uploadAgentDeviceArtifactAsync, () => { + beforeEach(() => { + jest.mocked(fetch).mockReset(); + jest.mocked(uploadDeviceRunSessionArtifactAsync).mockReset(); + }); + + it('downloads an agent-device artifact and uploads it as a device run session artifact', async () => { + const data = Buffer.from('artifact-data'); + const logger = createLoggerMock(); + const ctx = {} as unknown as CustomBuildContext; + + jest.mocked(fetch).mockResolvedValueOnce(new Response(Readable.from([data]))); + jest + .mocked(uploadDeviceRunSessionArtifactAsync) + .mockImplementationOnce(async (_ctx, { stream }) => { + await readStreamAsync(stream); + }); + + await uploadAgentDeviceArtifactAsync(ctx, { + deviceRunSessionId: 'drs-id', + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + logger, + artifact: { + id: 'artifact-id', + filename: 'report.json', + mimeType: 'application/json', + sizeBytes: data.length, + createdAt: '2026-07-02T12:00:00.000Z', + expiresAt: '2026-07-02T12:15:00.000Z', + }, + }); + + expect(jest.mocked(fetch)).toHaveBeenCalledWith('http://127.0.0.1:1234/artifacts/artifact-id', { + headers: { Authorization: 'Bearer daemon-token' }, + }); + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync)).toHaveBeenCalledWith(ctx, { + deviceRunSessionId: 'drs-id', + artifactId: 'artifact-id', + name: 'report.json (artifact-id)', + filename: 'report.json', + size: data.length, + stream: expect.anything(), + }); + }); +}); + +describe(pollAgentDeviceArtifactsForUploadAsync, () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.mocked(fetch).mockReset(); + jest.mocked(uploadDeviceRunSessionArtifactAsync).mockReset(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('retries an artifact after a failed upload', async () => { + const data = Buffer.from('artifact-data'); + const logger = createLoggerMock(); + const ctx = {} as unknown as CustomBuildContext; + const artifact = { + id: 'artifact-id', + filename: 'report.json', + mimeType: 'application/json', + sizeBytes: data.length, + createdAt: '2026-07-02T12:00:00.000Z', + expiresAt: '2026-07-02T12:15:00.000Z', + }; + const listResponse = () => new Response(JSON.stringify({ artifacts: [artifact] })); + + jest + .mocked(fetch) + .mockResolvedValueOnce(listResponse()) + .mockResolvedValueOnce(new Response(Readable.from([data]))) + .mockResolvedValueOnce(listResponse()) + .mockResolvedValueOnce(new Response(Readable.from([data]))); + jest + .mocked(uploadDeviceRunSessionArtifactAsync) + .mockRejectedValueOnce(new Error('upload failed')) + .mockImplementationOnce(async (_ctx, { stream }) => { + await readStreamAsync(stream); + }); + + void pollAgentDeviceArtifactsForUploadAsync(ctx, { + deviceRunSessionId: 'drs-id', + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + logger, + }); + + await flushPromisesAsync(); + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync)).toHaveBeenCalledTimes(1); + + await jest.advanceTimersByTimeAsync(5_000); + await flushPromisesAsync(); + + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync)).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts new file mode 100644 index 0000000000..17212179f4 --- /dev/null +++ b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts @@ -0,0 +1,201 @@ +import { SystemError } from '@expo/eas-build-job'; +import { type bunyan } from '@expo/logger'; +import { createReadStream, createWriteStream } from 'node:fs'; +import { mkdtemp, rm, stat } from 'node:fs/promises'; +import fetch from 'node-fetch'; +import os from 'node:os'; +import path from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { z } from 'zod'; + +import { CustomBuildContext } from '../../customBuildContext'; +import { Sentry } from '../../sentry'; +import { formatBytes } from '../../utils/artifacts'; +import { sleepAsync } from '../../utils/retry'; +import { uploadDeviceRunSessionArtifactAsync } from './deviceRunSessionArtifacts'; + +const AGENT_DEVICE_ARTIFACT_UPLOAD_POLL_INTERVAL_MS = 5_000; + +const AgentDeviceArtifactSchema = z.object({ + id: z.string(), + filename: z.string(), + mimeType: z.string(), + sizeBytes: z.number(), + createdAt: z.string(), + expiresAt: z.string(), +}); +const AgentDeviceArtifactsListResponseSchema = z.object({ + artifacts: z.array(AgentDeviceArtifactSchema), +}); + +type AgentDeviceArtifact = z.infer; + +class AgentDeviceArtifactsUnsupportedError extends Error { + constructor() { + super('agent-device daemon does not expose artifact inventory.'); + } +} + +export async function pollAgentDeviceArtifactsForUploadAsync( + ctx: CustomBuildContext, + { + deviceRunSessionId, + daemonUrl, + daemonToken, + logger, + }: { + deviceRunSessionId: string; + daemonUrl: string; + daemonToken: string; + logger: bunyan; + } +): Promise { + logger.info('Started polling agent-device daemon for artifacts.'); + const uploadingArtifactIds = new Set(); + const uploadedArtifactIds = new Set(); + let listArtifactsErrorCount = 0; + + for (;;) { + try { + const artifacts = await listAgentDeviceArtifactsAsync({ daemonUrl, daemonToken }); + listArtifactsErrorCount = 0; + for (const artifact of artifacts) { + if (uploadedArtifactIds.has(artifact.id) || uploadingArtifactIds.has(artifact.id)) { + continue; + } + uploadingArtifactIds.add(artifact.id); + void uploadAgentDeviceArtifactAsync(ctx, { + deviceRunSessionId, + daemonUrl, + daemonToken, + artifact, + logger, + }) + .then(() => { + uploadedArtifactIds.add(artifact.id); + }) + .catch(err => { + const error = err instanceof Error ? err : new Error(String(err)); + Sentry.capture('Could not upload agent-device remote session artifact', error); + logger.warn({ err: error }, 'Could not upload agent-device remote session artifact.'); + }) + .finally(() => { + uploadingArtifactIds.delete(artifact.id); + }); + } + } catch (err) { + if (err instanceof AgentDeviceArtifactsUnsupportedError) { + logger.warn( + 'agent-device daemon does not expose artifact inventory; remote session artifact uploads are disabled.' + ); + await new Promise(() => {}); + } + const error = err instanceof Error ? err : new Error(String(err)); + listArtifactsErrorCount += 1; + if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) { + Sentry.capture('Could not list agent-device remote session artifacts', error); + logger.warn( + { err: error, failedArtifactListCount: listArtifactsErrorCount }, + 'Could not list agent-device remote session artifacts.' + ); + } + } + await sleepAsync(AGENT_DEVICE_ARTIFACT_UPLOAD_POLL_INTERVAL_MS); + } +} + +export async function listAgentDeviceArtifactsAsync({ + daemonUrl, + daemonToken, +}: { + daemonUrl: string; + daemonToken: string; +}): Promise { + const response = await fetch(new URL('/artifacts', daemonUrl).toString(), { + headers: { Authorization: `Bearer ${daemonToken}` }, + }); + if (response.status === 404) { + throw new AgentDeviceArtifactsUnsupportedError(); + } + if (!response.ok) { + throw new SystemError( + `Failed to list agent-device artifacts: ${response.status} ${response.statusText}` + ); + } + const result = AgentDeviceArtifactsListResponseSchema.safeParse(await response.json()); + if (!result.success) { + throw new SystemError(`Invalid agent-device artifacts response: ${result.error.message}`); + } + return result.data.artifacts; +} + +export async function uploadAgentDeviceArtifactAsync( + ctx: CustomBuildContext, + { + deviceRunSessionId, + daemonUrl, + daemonToken, + artifact, + logger, + }: { + deviceRunSessionId: string; + daemonUrl: string; + daemonToken: string; + artifact: AgentDeviceArtifact; + logger: bunyan; + } +): Promise { + logger.info(`Downloading artifact ${artifact.filename}.`); + const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'agent-device-artifact-')); + try { + const temporaryArtifactPath = path.join(temporaryDirectory, path.basename(artifact.filename)); + await downloadAgentDeviceArtifactToFileAsync({ + artifact, + daemonUrl, + daemonToken, + destinationPath: temporaryArtifactPath, + }); + const { size } = await stat(temporaryArtifactPath); + logger.info(`Uploading artifact ${artifact.filename} (${formatBytes(size)}).`); + await uploadDeviceRunSessionArtifactAsync(ctx, { + deviceRunSessionId, + artifactId: artifact.id, + name: `${artifact.filename} (${artifact.id})`, + filename: artifact.filename, + size, + stream: createReadStream(temporaryArtifactPath), + }); + } finally { + await rm(temporaryDirectory, { recursive: true, force: true }); + } +} + +async function downloadAgentDeviceArtifactToFileAsync({ + artifact, + daemonUrl, + daemonToken, + destinationPath, +}: { + artifact: AgentDeviceArtifact; + daemonUrl: string; + daemonToken: string; + destinationPath: string; +}): Promise { + const response = await fetch( + new URL(`/artifacts/${encodeURIComponent(artifact.id)}`, daemonUrl).toString(), + { + headers: { Authorization: `Bearer ${daemonToken}` }, + } + ); + if (!response.ok) { + throw new SystemError( + `Failed to download agent-device artifact ${artifact.id}: ${response.status} ${response.statusText}` + ); + } + if (!response.body) { + throw new SystemError( + `Agent-device artifact ${artifact.id} response did not include a readable body.` + ); + } + await pipeline(response.body, createWriteStream(destinationPath)); +} From 9c229cda9d631d879762ab4cf1678d1f7dc978ec Mon Sep 17 00:00:00 2001 From: szdziedzic Date: Thu, 2 Jul 2026 21:11:01 +0200 Subject: [PATCH 3/3] update schema --- packages/eas-cli/graphql.schema.json | 385 +++++++++++++++++++++- packages/eas-cli/src/graphql/generated.ts | 57 +++- 2 files changed, 425 insertions(+), 17 deletions(-) diff --git a/packages/eas-cli/graphql.schema.json b/packages/eas-cli/graphql.schema.json index 88a7995356..948458fcca 100644 --- a/packages/eas-cli/graphql.schema.json +++ b/packages/eas-cli/graphql.schema.json @@ -419,6 +419,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "aiChatEnabled", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "appCount", "description": null, @@ -4278,8 +4294,8 @@ "deprecationReason": null }, { - "name": "setDisplayName", - "description": "Set the display name for the account.", + "name": "setAiChatEnabled", + "description": "Set whether the AI chat is enabled for this account.", "args": [ { "name": "accountID", @@ -4298,20 +4314,65 @@ "deprecationReason": null }, { - "name": "displayName", + "name": "aiChatEnabled", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Account", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "setDisplayName", + "description": "Set the display name for the account. Pass null to clear it and fall back to the account identifier.", + "args": [ + { + "name": "accountID", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", "ofType": null } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "displayName", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -15890,6 +15951,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "body", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "clientVersion", "description": null, @@ -22182,13 +22255,9 @@ "description": null, "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "RemoteAppStoreConnectApp", - "ofType": null - } + "kind": "OBJECT", + "name": "RemoteAppStoreConnectApp", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -22391,6 +22460,270 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AppStoreConnectBuild", + "description": null, + "fields": [ + { + "name": "ascBuildIdentifier", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "buildNumber", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "expirationDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "minOsVersion", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "processingState", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppStoreConnectBuildProcessingState", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uploadedDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "AppStoreConnectBuildProcessingState", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "FAILED", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INVALID", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PROCESSING", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VALID", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AppStoreConnectBuildUpload", + "description": null, + "fields": [ + { + "name": "appStoreConnectBuild", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "AppStoreConnectBuild", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ascBuildUploadIdentifier", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "buildNumber", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "createdDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uploadState", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "AppStoreConnectBuildUploadState", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uploadedDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "version", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "AppStoreConnectBuildUploadState", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "AWAITING_UPLOAD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "COMPLETE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FAILED", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PROCESSING", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "ENUM", "name": "AppStoreConnectUserRole", @@ -49003,6 +49336,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "sourceIpaUrl", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -68938,6 +69287,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "appStoreConnectBuildUpload", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "AppStoreConnectBuildUpload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "archiveUrl", "description": null, @@ -86912,7 +87273,7 @@ { "kind": "OBJECT", "name": "WorkflowDeviceTestCaseInsightsMetric", - "description": "A count metric returned for the current window AND the equivalent prior window\n(start − duration → start). The frontend uses (current, previous) to compute\ntrend deltas. Trend ratios are NOT pre-computed server-side — see\nWorkflowsInsightsMetric for the equivalent convention.\n\nFloat (not Int) to match WorkflowsInsightsMetric and avoid the GraphQL Int32\nceiling: 90-day uniqExact counts on a high-volume project can plausibly\nexceed 2.1B. Values are integer-valued; the frontend reads them as JS numbers.", + "description": "A count metric returned for the current window AND the equivalent prior window\n(start − duration → start). The frontend uses (current, previous) to compute\ntrend deltas. Trend ratios are NOT pre-computed server-side — see\nWorkflowsInsightsMetric for the equivalent convention.\n\nFloat (not Int) to match WorkflowsInsightsMetric and avoid the GraphQL Int32\nceiling: year-long uniqExact counts on a high-volume project can plausibly\nexceed 2.1B. Values are integer-valued; the frontend reads them as JS numbers.", "fields": [ { "name": "currentValue", diff --git a/packages/eas-cli/src/graphql/generated.ts b/packages/eas-cli/src/graphql/generated.ts index 7b1ef6db0f..181909e568 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -85,6 +85,7 @@ export type Account = { accountFeatureGates: Scalars['JSONObject']['output']; /** Coalesced project activity for all apps belonging to this account. */ activityTimelineProjectActivities: Array; + aiChatEnabled: Scalars['Boolean']['output']; appCount: Scalars['Int']['output']; /** @deprecated Use appStoreConnectApiKeysPaginated */ appStoreConnectApiKeys: Array; @@ -691,7 +692,9 @@ export type AccountMutation = { requestRefund?: Maybe; /** Revoke specified Permissions for Actor. Actor must already have at least one permission on the account. */ revokeActorPermissions: Account; - /** Set the display name for the account. */ + /** Set whether the AI chat is enabled for this account. */ + setAiChatEnabled: Account; + /** Set the display name for the account. Pass null to clear it and fall back to the account identifier. */ setDisplayName: Account; /** Require authorization to send push notifications for experiences owned by this account */ setPushSecurityEnabled: Account; @@ -769,9 +772,15 @@ export type AccountMutationRevokeActorPermissionsArgs = { }; +export type AccountMutationSetAiChatEnabledArgs = { + accountID: Scalars['ID']['input']; + aiChatEnabled: Scalars['Boolean']['input']; +}; + + export type AccountMutationSetDisplayNameArgs = { accountID: Scalars['ID']['input']; - displayName: Scalars['String']['input']; + displayName?: InputMaybe; }; @@ -2439,6 +2448,7 @@ export type AppObserveCustomEvent = { appUpdateId?: Maybe; appUpdateMessage?: Maybe; appVersion: Scalars['String']['output']; + body?: Maybe; clientVersion?: Maybe; countryCode?: Maybe; deviceLanguageTag?: Maybe; @@ -3189,7 +3199,7 @@ export type AppStoreConnectApp = { ascAppIdentifier: Scalars['String']['output']; createdAt: Scalars['DateTime']['output']; id: Scalars['ID']['output']; - remoteAppStoreConnectApp: RemoteAppStoreConnectApp; + remoteAppStoreConnectApp?: Maybe; updatedAt: Scalars['DateTime']['output']; webhookEventTypes: Array; webhookIdentifier: Scalars['ID']['output']; @@ -3219,6 +3229,41 @@ export type AppStoreConnectAppMutationDeleteAppStoreConnectAppArgs = { appStoreConnectAppId: Scalars['ID']['input']; }; +export type AppStoreConnectBuild = { + __typename?: 'AppStoreConnectBuild'; + ascBuildIdentifier: Scalars['String']['output']; + buildNumber?: Maybe; + expirationDate?: Maybe; + minOsVersion?: Maybe; + processingState: AppStoreConnectBuildProcessingState; + uploadedDate?: Maybe; +}; + +export enum AppStoreConnectBuildProcessingState { + Failed = 'FAILED', + Invalid = 'INVALID', + Processing = 'PROCESSING', + Valid = 'VALID' +} + +export type AppStoreConnectBuildUpload = { + __typename?: 'AppStoreConnectBuildUpload'; + appStoreConnectBuild?: Maybe; + ascBuildUploadIdentifier: Scalars['String']['output']; + buildNumber?: Maybe; + createdDate?: Maybe; + uploadState: AppStoreConnectBuildUploadState; + uploadedDate?: Maybe; + version?: Maybe; +}; + +export enum AppStoreConnectBuildUploadState { + AwaitingUpload = 'AWAITING_UPLOAD', + Complete = 'COMPLETE', + Failed = 'FAILED', + Processing = 'PROCESSING' +} + export enum AppStoreConnectUserRole { AccessToReports = 'ACCESS_TO_REPORTS', AccountHolder = 'ACCOUNT_HOLDER', @@ -7007,6 +7052,7 @@ export type ExpoGoSdkVersion = { isDeprecated: Scalars['Boolean']['output']; isLatest: Scalars['Boolean']['output']; sdkVersion: Scalars['String']['output']; + sourceIpaUrl: Scalars['String']['output']; }; export type FcmSnippet = FcmSnippetLegacy | FcmSnippetV1; @@ -9763,6 +9809,7 @@ export type Submission = ActivityTimelineProjectActivity & { actor?: Maybe; androidConfig?: Maybe; app: App; + appStoreConnectBuildUpload?: Maybe; archiveUrl?: Maybe; canRetry: Scalars['Boolean']['output']; cancelingActor?: Maybe; @@ -12157,7 +12204,7 @@ export type WorkflowDeviceTestCaseInsightsFiltersInput = { * WorkflowsInsightsMetric for the equivalent convention. * * Float (not Int) to match WorkflowsInsightsMetric and avoid the GraphQL Int32 - * ceiling: 90-day uniqExact counts on a high-volume project can plausibly + * ceiling: year-long uniqExact counts on a high-volume project can plausibly * exceed 2.1B. Values are integer-valued; the frontend reads them as JS numbers. */ export type WorkflowDeviceTestCaseInsightsMetric = { @@ -13816,7 +13863,7 @@ export type AscAppLinkAppMetadataQueryVariables = Exact<{ }>; -export type AscAppLinkAppMetadataQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, fullName: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'PartnerActor', id: string } | { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> }, appStoreConnectApp?: { __typename?: 'AppStoreConnectApp', id: string, ascAppIdentifier: string, remoteAppStoreConnectApp: { __typename?: 'RemoteAppStoreConnectApp', ascAppIdentifier: string, bundleIdentifier: string, name?: string | null, appStoreIconUrl?: string | null } } | null } } }; +export type AscAppLinkAppMetadataQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, fullName: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'PartnerActor', id: string } | { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> }, appStoreConnectApp?: { __typename?: 'AppStoreConnectApp', id: string, ascAppIdentifier: string, remoteAppStoreConnectApp?: { __typename?: 'RemoteAppStoreConnectApp', ascAppIdentifier: string, bundleIdentifier: string, name?: string | null, appStoreIconUrl?: string | null } | null } | null } } }; export type DiscoverAccessibleAppStoreConnectAppsQueryVariables = Exact<{ appStoreConnectApiKeyId: Scalars['ID']['input'];