From c4c42dd0843126657185e95b752c0f7adbc82188 Mon Sep 17 00:00:00 2001 From: szdziedzic Date: Thu, 1 Oct 2026 10:42:44 +0200 Subject: [PATCH 1/2] [build-tools] Upload the agent-device screenshot display rotation as artifact metadata agent-device reports displayRotation on screenshot entries in its /artifacts inventory. Read it and pass it to the device run session artifact upload as metadata.displayRotation. An unknown value is dropped so it cannot block the upload, and an artifact without the field uploads no metadata, as before. Co-Authored-By: Claude Opus 5.5 --- .../__tests__/agentDeviceArtifacts.test.ts | 90 +++++++++++++++++++ .../src/steps/utils/agentDeviceArtifacts.ts | 8 ++ 2 files changed, 98 insertions(+) diff --git a/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts index 94abed713f..7ab31ee117 100644 --- a/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts @@ -104,6 +104,39 @@ describe(listAgentDeviceArtifactsAsync, () => { }) ).rejects.toThrow('agent-device daemon does not expose artifact inventory.'); }); + + it('keeps a screenshot display rotation and drops an unknown one', async () => { + jest.mocked(fetch).mockResolvedValue( + new Response( + JSON.stringify({ + artifacts: [ + { + id: 'rotated', + artifactType: 'screenshot', + filename: 'rotated.png', + displayRotation: 'landscape-left', + }, + { + id: 'future', + artifactType: 'screenshot', + filename: 'future.png', + displayRotation: 'sideways', + }, + ], + }) + ) + ); + + const artifacts = await listAgentDeviceArtifactsAsync({ + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + }); + + expect(artifacts.map(artifact => artifact.displayRotation)).toEqual([ + 'landscape-left', + undefined, + ]); + }); }); describe(uploadAgentDeviceArtifactAsync, () => { @@ -181,6 +214,63 @@ describe(uploadAgentDeviceArtifactAsync, () => { }) ); }); + + it('uploads a screenshot display rotation as artifact metadata', async () => { + const data = Buffer.from('png-data'); + 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: createLoggerMock(), + artifact: { + id: 'artifact-id', + artifactType: 'screenshot', + filename: 'shot.png', + displayRotation: 'landscape-right', + }, + }); + + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync)).toHaveBeenCalledWith( + ctx, + expect.objectContaining({ + kind: 'screenshot', + metadata: { displayRotation: 'landscape-right' }, + }) + ); + }); + + it('uploads no metadata when the artifact has no display rotation', async () => { + const data = Buffer.from('artifact-data'); + 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: createLoggerMock(), + artifact: { id: 'artifact-id', artifactType: 'screenshot', filename: 'shot.png' }, + }); + + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync).mock.calls[0][1]).not.toHaveProperty( + 'metadata' + ); + }); }); describe(pollAgentDeviceArtifactsForUploadAsync, () => { diff --git a/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts index 873dd94f9c..926b9caaf1 100644 --- a/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts +++ b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts @@ -20,6 +20,11 @@ const AgentDeviceArtifactSchema = z.object({ id: z.string(), artifactType: z.string().nullish(), filename: z.string(), + // Display rotation of a screenshot. An unknown value is dropped so it cannot block the upload. + displayRotation: z + .enum(['portrait', 'portrait-upside-down', 'landscape-left', 'landscape-right']) + .nullish() + .catch(undefined), }); const AgentDeviceArtifactsListResponseSchema = z.object({ artifacts: z.array(AgentDeviceArtifactSchema), @@ -154,6 +159,9 @@ export async function uploadAgentDeviceArtifactAsync( name: `${artifact.filename} (${artifact.id})`, filename: artifact.filename, kind: artifact.artifactType ?? undefined, + ...(artifact.displayRotation + ? { metadata: { displayRotation: artifact.displayRotation } } + : {}), size, stream: createReadStream(temporaryArtifactPath), }); From b97a0f5e02e31a0386256c0bf6cf1b89ef6e3789 Mon Sep 17 00:00:00 2001 From: szdziedzic Date: Thu, 1 Oct 2026 10:43:12 +0200 Subject: [PATCH 2/2] [changelog] Add entry for #4508 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1471f7e910..9c50d97820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This is the log of notable changes to EAS CLI and related packages. - [build-tools] Record iOS Simulator sessions through serve-sim. ([#4474](https://github.com/expo/eas-cli/pull/4474) by [@gwdp](https://github.com/gwdp)) - [eas-cli] Add browser device login with resumable commands for chat agents. ([#4502](https://github.com/expo/eas-cli/pull/4502) by [@eiiot](https://github.com/eiiot)) +- [build-tools] Upload the display rotation agent-device reports for a screenshot as device run session artifact metadata. ([#4508](https://github.com/expo/eas-cli/pull/4508) by [@szdziedzic](https://github.com/szdziedzic)) ### 🐛 Bug fixes