Compare commits

...
Author SHA1 Message Date
Expo CI dcc7201ca8 v21.4.0 2026-07-28 10:18:26 +00:00
Szymon Dziedzic bf3f259bc4 [eas-cli] Support naming simulator sessions (#4099)
* [eas-cli] Support naming simulator sessions

* [eas-cli] Trim the simulator session name flag
2026-07-28 12:14:06 +02:00
Szymon DziedzicandClaude Opus 4.8 1a573679e5 [eas-cli] Add simulator session activity command (#4001)
* [eas-cli] Add simulator session activity command

* [eas-cli] Read simulator events from artifacts

* [eas-cli] Read session event artifact type metadata

* [eas-cli] Read compact session event records

* [eas-cli] Align simulator event output

* [eas-cli] Harden simulator event following

* [eas-build-job] Share device run session event schema

Move the DeviceRunSession session-events record schema, type, and NDJSON
parser out of eas-cli into @expo/eas-build-job so producers (worker,
agent-device) and consumers (eas-cli, website) share a single source of
truth for the wire format. eas-cli re-exports the type and keeps the
CLI-only display, formatting, and download logic.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* [eas-cli] Regenerate GraphQL schema and types

Run `yarn generate-graphql-code` to refresh the committed schema snapshot
and generated types against the current API.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-07-28 11:10:02 +02:00
Douglas Lowder bb589f1d36 [eas-cli] Apply --session-id in observe:events summary mode (#4100)
# Summary

Without an event name, observe:events showed the account-wide event-name summary, which has no session filter, so --session-id was ignored. When a session ID is given, take the events-list path (like --all-events) so the
session's events are shown filtered by the customEventList session filter.

# Test Plan

- New unit tests added
- Manual testing against a production project
2026-07-27 18:25:17 -07:00
30 changed files with 2632 additions and 164 deletions

No files matched your search

+3
View File
@@ -8,10 +8,13 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- [eas-cli] Add experimental `eas simulator:events` command. ([#4001](https://github.com/expo/eas-cli/pull/4001) by [@szdziedzic](https://github.com/szdziedzic))
- [build-tools] Collect and upload Argent tool-server session events during remote simulator sessions, mirroring agent-device event collection. ([#4067](https://github.com/expo/eas-cli/pull/4067) by [@szdziedzic](https://github.com/szdziedzic))
- [eas-cli] Add `--name` to `eas simulator:start` and show simulator session names in `eas simulator:get` and `eas simulator:list`. ([#4099](https://github.com/expo/eas-cli/pull/4099) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- [eas-cli] Apply --session-id in observe:events summary mode. ([#4100](https://github.com/expo/eas-cli/pull/4100) by [@douglowder](https://github.com/douglowder))
- [eas-cli] Suggest running `agent-device` via `npx` in EAS Simulator session instructions. ([#4070](https://github.com/expo/eas-cli/pull/4070) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "21.3.0",
"version": "21.4.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/build-tools",
"version": "21.3.0",
"version": "21.4.0",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
"author": "Expo <[email protected]>",
@@ -39,14 +39,14 @@
"@expo/config": "55.0.10",
"@expo/config-plugins": "55.0.7",
"@expo/downloader": "21.0.0",
"@expo/eas-build-job": "21.3.0",
"@expo/eas-build-job": "21.4.0",
"@expo/env": "^0.4.0",
"@expo/logger": "21.0.0",
"@expo/package-manager": "1.9.10",
"@expo/plist": "^0.3.5",
"@expo/results": "^1.0.0",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "21.3.0",
"@expo/steps": "21.4.0",
"@expo/template-file": "21.0.2",
"@expo/turtle-spawn": "21.0.0",
"@expo/xcpretty": "^4.3.1",
@@ -1,6 +1,6 @@
{
"name": "create-eas-build-function",
"version": "21.3.0",
"version": "21.4.0",
"description": "Create functions for use in EAS Build custom builds.",
"keywords": [
"expo",
@@ -35,7 +35,7 @@
"clean": "rimraf ./build/ \"*.tsbuildinfo\""
},
"dependencies": {
"@expo/steps": "21.3.0"
"@expo/steps": "21.4.0"
},
"devDependencies": {
"@expo/package-manager": "1.9.10",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/eas-build-job",
"version": "21.3.0",
"version": "21.4.0",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "MIT",
"author": "Expo <[email protected]>",
@@ -0,0 +1,50 @@
import { DeviceRunSessionEvent, parseDeviceRunSessionEvents } from '../deviceRunSession';
describe('parseDeviceRunSessionEvents', () => {
it('parses, deduplicates, and sorts events from the dedicated event log', () => {
const first = createEvent({
eventId: 'first',
ts: '2026-07-10T12:00:00.000Z',
});
const second = createEvent({
eventId: 'second',
ts: '2026-07-10T12:00:01.000Z',
});
expect(
parseDeviceRunSessionEvents(`${second}\nplain text\n${first}\n${second}`).map(
({ eventId }) => eventId
)
).toEqual(['first', 'second']);
});
it('ignores malformed events', () => {
expect(parseDeviceRunSessionEvents(`plain text\n${JSON.stringify({ v: 1 })}`)).toEqual([]);
});
it('ignores events with invalid timestamps', () => {
expect(
parseDeviceRunSessionEvents(
`${createEvent({ eventId: 'invalid', ts: 'not-a-date' })}\n${createEvent({
eventId: 'valid',
})}`
).map(({ eventId }) => eventId)
).toEqual(['valid']);
});
});
function createEvent(overrides: Partial<DeviceRunSessionEvent> = {}): string {
return JSON.stringify(createEventValue(overrides));
}
function createEventValue(overrides: Partial<DeviceRunSessionEvent> = {}): DeviceRunSessionEvent {
return {
v: 1,
eventId: 'event-id',
ts: '2026-07-10T12:00:00.000Z',
producer: 'agent-device',
type: 'operation.started',
summary: 'Started tap',
...overrides,
};
}
@@ -0,0 +1,48 @@
import { z } from 'zod';
/**
* Canonical record format for the activity events emitted during a remote device run (simulator)
* session. Producers (e.g. the worker and agent-device) write these as NDJSON to the
* session-events artifact and consumers (e.g. EAS CLI and the website) read them back. Keep this
* schema in sync across every producer and consumer.
*/
export const DeviceRunSessionEventZ = z
.object({
v: z.literal(1),
eventId: z.string(),
ts: z.string().refine(ts => !Number.isNaN(new Date(ts).getTime())),
producer: z.string(),
type: z.string(),
operationId: z.string().optional(),
outcome: z.enum(['success', 'failure']).optional(),
durationMs: z.number().optional(),
summary: z.string(),
data: z.record(z.string(), z.unknown()).optional(),
})
.passthrough();
export type DeviceRunSessionEvent = z.infer<typeof DeviceRunSessionEventZ>;
/**
* Parse the NDJSON session-events log into canonical event records, de-duplicating by `eventId`
* and sorting chronologically by `ts`. Malformed or incomplete lines (including records with
* invalid timestamps) are ignored so that a partially-flushed log is still readable.
*/
export function parseDeviceRunSessionEvents(eventLog: string): DeviceRunSessionEvent[] {
const events = new Map<string, DeviceRunSessionEvent>();
for (const line of eventLog.split('\n')) {
try {
const result = DeviceRunSessionEventZ.safeParse(JSON.parse(line));
if (result.success) {
events.set(result.data.eventId, result.data);
}
} catch {
// Ignore malformed or incomplete records.
}
}
return [...events.values()].sort(
(left, right) => new Date(left.ts).getTime() - new Date(right.ts).getTime()
);
}
+1
View File
@@ -29,6 +29,7 @@ export * from './step';
export * from './compositeFunction';
export * from './submission-config';
export * from './projectPackage';
export * from './deviceRunSession';
const version = require('../package.json').version;
export { version };
+124 -121
View File
@@ -272,7 +272,7 @@ DESCRIPTION
view the audit logs for an account
```
_See code: [packages/eas-cli/src/commands/account/audit.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/account/audit.ts)_
_See code: [packages/eas-cli/src/commands/account/audit.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/account/audit.ts)_
## `eas account:login`
@@ -293,7 +293,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/account/login.ts)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -310,7 +310,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:usage [ACCOUNT_NAME]`
@@ -332,7 +332,7 @@ DESCRIPTION
view account usage and billing for the current cycle
```
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/account/usage.ts)_
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/account/usage.ts)_
## `eas account:view`
@@ -349,7 +349,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/account/view.ts)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -363,7 +363,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -394,7 +394,7 @@ EXAMPLES
$ eas autocomplete --refresh-cache
```
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.40/packages/eas-cli/src/commands/autocomplete/index.ts)_
_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.40/src/commands/autocomplete/index.ts)_
## `eas branch:create [NAME]`
@@ -415,7 +415,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -436,7 +436,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -456,7 +456,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -476,7 +476,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -499,7 +499,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas browse [PAGE]`
@@ -522,7 +522,7 @@ DESCRIPTION
Transition from the terminal to the web browser to view and interact with your project on https://expo.dev
```
_See code: [packages/eas-cli/src/commands/browse.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/browse.ts)_
_See code: [packages/eas-cli/src/commands/browse.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/browse.ts)_
## `eas build`
@@ -563,7 +563,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/index.ts)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -583,7 +583,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -601,7 +601,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -621,7 +621,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/delete.ts)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:dev`
@@ -646,7 +646,7 @@ DESCRIPTION
run dev client simulator/emulator build with matching fingerprint or create a new one
```
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/dev.ts)_
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/dev.ts)_
## `eas build:download`
@@ -673,7 +673,7 @@ DESCRIPTION
download a simulator/emulator build by build ID or fingerprint hash
```
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/download.ts)_
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/download.ts)_
## `eas build:inspect`
@@ -711,7 +711,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -751,7 +751,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/list.ts)_
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -781,7 +781,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -809,7 +809,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/run.ts)_
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -861,7 +861,7 @@ DESCRIPTION
get the latest version from EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -879,7 +879,7 @@ DESCRIPTION
update version of an app
```
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -897,7 +897,7 @@ DESCRIPTION
update a version in native code with a value stored on EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -914,7 +914,7 @@ DESCRIPTION
view a build for your project
```
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/build/view.ts)_
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -935,7 +935,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:delete [NAME]`
@@ -956,7 +956,7 @@ DESCRIPTION
Delete a channel
```
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/delete.ts)_
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/delete.ts)_
## `eas channel:edit [NAME]`
@@ -978,7 +978,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:insights`
@@ -1002,7 +1002,7 @@ DESCRIPTION
display adoption, crash, and unique-user insights for a channel + runtime version
```
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/insights.ts)_
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/insights.ts)_
## `eas channel:list`
@@ -1022,7 +1022,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:pause [NAME]`
@@ -1044,7 +1044,7 @@ DESCRIPTION
pause a channel to stop it from sending updates
```
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/pause.ts)_
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/pause.ts)_
## `eas channel:resume [NAME]`
@@ -1066,7 +1066,7 @@ DESCRIPTION
resume a channel to start sending updates
```
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/resume.ts)_
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/resume.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -1101,7 +1101,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally.
```
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -1124,7 +1124,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -1145,7 +1145,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -1162,7 +1162,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/credentials/index.ts)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -1180,7 +1180,7 @@ DESCRIPTION
Set up credentials for building your project.
```
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas deploy [options]`
@@ -1212,7 +1212,7 @@ ALIASES
$ eas worker:deploy
```
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/deploy/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/deploy/index.ts)_
## `eas deploy:alias`
@@ -1237,7 +1237,7 @@ ALIASES
$ eas deploy:promote
```
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
## `eas deploy:alias:delete [ALIAS_NAME]`
@@ -1258,7 +1258,7 @@ ALIASES
$ eas worker:alias:delete
```
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
## `eas deploy:delete [DEPLOYMENT_ID]`
@@ -1279,7 +1279,7 @@ ALIASES
$ eas worker:delete
```
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/deploy/delete.ts)_
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/deploy/delete.ts)_
## `eas deploy:promote`
@@ -1316,7 +1316,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/device/create.ts)_
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -1336,7 +1336,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -1357,7 +1357,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/device/list.ts)_
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -1378,7 +1378,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -1392,7 +1392,7 @@ DESCRIPTION
view a device for your project
```
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/device/view.ts)_
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -1406,7 +1406,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas env:delete [ENVIRONMENT]`
@@ -1432,7 +1432,7 @@ DESCRIPTION
delete an environment variable for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/delete.ts)_
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/delete.ts)_
## `eas env:exec ENVIRONMENT BASH_COMMAND`
@@ -1454,7 +1454,7 @@ DESCRIPTION
execute a command with environment variables from the selected environment
```
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/exec.ts)_
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/exec.ts)_
## `eas env:get [ENVIRONMENT]`
@@ -1482,7 +1482,7 @@ DESCRIPTION
view an environment variable for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/get.ts)_
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/get.ts)_
## `eas env:list [ENVIRONMENT]`
@@ -1510,7 +1510,7 @@ DESCRIPTION
list environment variables for the current project or account
```
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/list.ts)_
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/list.ts)_
## `eas env:pull [ENVIRONMENT]`
@@ -1533,7 +1533,7 @@ DESCRIPTION
pull environment variables for the selected environment to .env file
```
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/pull.ts)_
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/pull.ts)_
## `eas env:push [ENVIRONMENT]`
@@ -1555,7 +1555,7 @@ DESCRIPTION
push environment variables from .env file to the selected environment
```
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/push.ts)_
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/push.ts)_
## `eas env:set [ENVIRONMENT]`
@@ -1587,7 +1587,7 @@ DESCRIPTION
set (create or update) an environment variable on the current project or account
```
_See code: [packages/eas-cli/src/commands/env/set.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/env/set.ts)_
_See code: [packages/eas-cli/src/commands/env/set.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/env/set.ts)_
## `eas fingerprint:compare [HASH1] [HASH2]`
@@ -1631,7 +1631,7 @@ EXAMPLES
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
```
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
## `eas fingerprint:generate`
@@ -1662,7 +1662,7 @@ EXAMPLES
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
```
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
## `eas help [COMMAND]`
@@ -1682,7 +1682,7 @@ DESCRIPTION
Display help for eas.
```
_See code: [@expo/plugin-help](https://github.com/expo/oclif-plugin-help/blob/v5.1.23/packages/eas-cli/src/commands/help.ts)_
_See code: [@expo/plugin-help](https://github.com/expo/oclif-plugin-help/blob/v5.1.23/src/commands/help.ts)_
## `eas init`
@@ -1736,7 +1736,7 @@ DESCRIPTION
connect a project to an App Store Connect app
```
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
## `eas integrations:asc:disconnect`
@@ -1755,7 +1755,7 @@ DESCRIPTION
disconnect the current project from its App Store Connect app
```
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
## `eas integrations:asc:status`
@@ -1773,7 +1773,7 @@ DESCRIPTION
show the App Store Connect app link status for the current project
```
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
## `eas integrations:convex:connect`
@@ -1795,7 +1795,7 @@ DESCRIPTION
connect Convex to your Expo project
```
_See code: [packages/eas-cli/src/commands/integrations/convex/connect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/connect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
## `eas integrations:convex:dashboard`
@@ -1809,7 +1809,7 @@ DESCRIPTION
open the Convex dashboard for the linked Convex project
```
_See code: [packages/eas-cli/src/commands/integrations/convex/dashboard.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/dashboard.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
## `eas integrations:convex:project`
@@ -1823,7 +1823,7 @@ DESCRIPTION
display the Convex project linked to the current Expo app
```
_See code: [packages/eas-cli/src/commands/integrations/convex/project.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/project.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
## `eas integrations:convex:project:delete`
@@ -1841,7 +1841,7 @@ DESCRIPTION
remove the Convex project link for the current Expo app from EAS servers
```
_See code: [packages/eas-cli/src/commands/integrations/convex/project/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/project/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
## `eas integrations:convex:team`
@@ -1855,7 +1855,7 @@ DESCRIPTION
display Convex teams linked to the current Expo app's owner account
```
_See code: [packages/eas-cli/src/commands/integrations/convex/team.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/team.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
## `eas integrations:convex:team:delete [CONVEX_TEAM]`
@@ -1876,7 +1876,7 @@ DESCRIPTION
remove a Convex team link from the current Expo app owner account's EAS servers
```
_See code: [packages/eas-cli/src/commands/integrations/convex/team/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/team/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
## `eas integrations:convex:team:invite [CONVEX_TEAM]`
@@ -1896,7 +1896,7 @@ DESCRIPTION
send a Convex team invitation to your verified email address
```
_See code: [packages/eas-cli/src/commands/integrations/convex/team/invite.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
_See code: [packages/eas-cli/src/commands/integrations/convex/team/invite.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
## `eas integrations:posthog:connect`
@@ -1923,7 +1923,7 @@ DESCRIPTION
connect PostHog to your Expo project
```
_See code: [packages/eas-cli/src/commands/integrations/posthog/connect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/posthog/connect.ts)_
_See code: [packages/eas-cli/src/commands/integrations/posthog/connect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/posthog/connect.ts)_
## `eas integrations:posthog:dashboard`
@@ -1943,7 +1943,7 @@ DESCRIPTION
open the PostHog dashboard for the linked PostHog project
```
_See code: [packages/eas-cli/src/commands/integrations/posthog/dashboard.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/posthog/dashboard.ts)_
_See code: [packages/eas-cli/src/commands/integrations/posthog/dashboard.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/posthog/dashboard.ts)_
## `eas integrations:posthog:disconnect`
@@ -1962,7 +1962,7 @@ DESCRIPTION
remove the PostHog project link for the current Expo app from EAS servers
```
_See code: [packages/eas-cli/src/commands/integrations/posthog/disconnect.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/integrations/posthog/disconnect.ts)_
_See code: [packages/eas-cli/src/commands/integrations/posthog/disconnect.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/integrations/posthog/disconnect.ts)_
## `eas login`
@@ -2014,7 +2014,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -2032,7 +2032,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -2050,7 +2050,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas new [PATH]`
@@ -2100,7 +2100,8 @@ FLAGS
--platform=<option> Filter by platform
<options: android|ios>
--project-id=<value> EAS project ID (defaults to the project ID of the current directory)
--session-id=<value> Filter by session ID
--session-id=<value> Filter by session ID. When no event name is given, lists the events in the session instead of
the event-name summary.
--start=<value> Start of time range (ISO date)
--update-id=<value> Filter by EAS update ID
@@ -2109,7 +2110,7 @@ DESCRIPTION
arguments, a list of the available event names and associated event counts is returned.
```
_See code: [packages/eas-cli/src/commands/observe/events.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/events.ts)_
_See code: [packages/eas-cli/src/commands/observe/events.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/events.ts)_
## `eas observe:metrics [METRIC]`
@@ -2145,7 +2146,7 @@ DESCRIPTION
display individual performance metric samples ordered by value
```
_See code: [packages/eas-cli/src/commands/observe/metrics.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/metrics.ts)_
_See code: [packages/eas-cli/src/commands/observe/metrics.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/metrics.ts)_
## `eas observe:metrics-summary`
@@ -2177,7 +2178,7 @@ DESCRIPTION
display aggregated performance metric statistics grouped by app version
```
_See code: [packages/eas-cli/src/commands/observe/metrics-summary.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/metrics-summary.ts)_
_See code: [packages/eas-cli/src/commands/observe/metrics-summary.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/metrics-summary.ts)_
## `eas observe:routes`
@@ -2214,7 +2215,7 @@ DESCRIPTION
display app navigation route metrics (Cold TTR, Warm TTR, TTI) grouped by route name
```
_See code: [packages/eas-cli/src/commands/observe/routes.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/routes.ts)_
_See code: [packages/eas-cli/src/commands/observe/routes.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/routes.ts)_
## `eas observe:session [SESSIONID]`
@@ -2245,7 +2246,7 @@ DESCRIPTION
display the timeline of metric and log events for a specific session
```
_See code: [packages/eas-cli/src/commands/observe/session.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/session.ts)_
_See code: [packages/eas-cli/src/commands/observe/session.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/session.ts)_
## `eas observe:versions`
@@ -2270,7 +2271,7 @@ DESCRIPTION
display app versions with build and update details
```
_See code: [packages/eas-cli/src/commands/observe/versions.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/observe/versions.ts)_
_See code: [packages/eas-cli/src/commands/observe/versions.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/observe/versions.ts)_
## `eas project:delete [NAME]`
@@ -2294,7 +2295,7 @@ DESCRIPTION
delete a project
```
_See code: [packages/eas-cli/src/commands/project/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/project/delete.ts)_
_See code: [packages/eas-cli/src/commands/project/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/project/delete.ts)_
## `eas project:icon:set PATH`
@@ -2314,7 +2315,7 @@ DESCRIPTION
set the project icon displayed on the EAS dashboard
```
_See code: [packages/eas-cli/src/commands/project/icon/set.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/project/icon/set.ts)_
_See code: [packages/eas-cli/src/commands/project/icon/set.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/project/icon/set.ts)_
## `eas project:info`
@@ -2328,7 +2329,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/project/info.ts)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -2362,7 +2363,7 @@ EXAMPLES
$ eas init --account my-account --json --non-interactive # Same, and print the result as JSON to stdout
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/project/init.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:new [PATH]`
@@ -2386,7 +2387,7 @@ ALIASES
$ eas new
```
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/project/new.ts)_
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/project/new.ts)_
## `eas simulator`
@@ -2394,8 +2395,8 @@ _See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/exp
```
USAGE
$ eas simulator [-p android|ios] [--type agent-device|argent|serve-sim] [--package-version <value>]
[--max-duration-minutes <value>] [--force] [--out-config-type env|dotenv] [--json] [--non-interactive]
$ eas simulator [-p android|ios] [--name <value>] [--type agent-device|argent|serve-sim] [--package-version
<value>] [--max-duration-minutes <value>] [--force] [--out-config-type env|dotenv] [--json] [--non-interactive]
FLAGS
-p, --platform=<option> Device platform
@@ -2407,6 +2408,8 @@ FLAGS
--max-duration-minutes=<value> Maximum duration of the simulator session in minutes before it is automatically
stopped. Only customizable on paid plans. Defaults to a value derived from the job
run priority when omitted.
--name=<value> Human-readable name for the simulator session, shown in eas simulator:list and on
expo.dev. Defaults to unnamed.
--non-interactive Run the command in non-interactive mode.
--out-config-type=<option> [default: dotenv] How to output simulator connection configuration. Use "env" to
print shell exports, or "dotenv" to write .env.eas-simulator.
@@ -2454,7 +2457,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/submit.ts)_
## `eas testflight:crashes [ID]`
@@ -2495,7 +2498,7 @@ EXAMPLES
$ eas testflight:crashes ${{ app_store_connect.beta_feedback.url }} --json # Look up whatever an EAS workflow trigger reported
```
_See code: [packages/eas-cli/src/commands/testflight/crashes.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/testflight/crashes.ts)_
_See code: [packages/eas-cli/src/commands/testflight/crashes.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/testflight/crashes.ts)_
## `eas testflight:feedback [ID]`
@@ -2537,7 +2540,7 @@ EXAMPLES
$ eas testflight:feedback ${{ app_store_connect.beta_feedback.url }} --json # Look up whatever an EAS workflow trigger reported
```
_See code: [packages/eas-cli/src/commands/testflight/feedback.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/testflight/feedback.ts)_
_See code: [packages/eas-cli/src/commands/testflight/feedback.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/testflight/feedback.ts)_
## `eas update`
@@ -2580,7 +2583,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/index.ts)_
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -2601,7 +2604,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -2622,7 +2625,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:edit [GROUPID]`
@@ -2647,7 +2650,7 @@ DESCRIPTION
edit all the updates in an update group
```
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/edit.ts)_
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/edit.ts)_
## `eas update:embedded:delete ID`
@@ -2668,7 +2671,7 @@ DESCRIPTION
delete an embedded update registered with EAS Update
```
_See code: [packages/eas-cli/src/commands/update/embedded/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/embedded/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/embedded/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/embedded/delete.ts)_
## `eas update:embedded:list`
@@ -2694,7 +2697,7 @@ DESCRIPTION
list embedded updates registered with EAS Update for this project
```
_See code: [packages/eas-cli/src/commands/update/embedded/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/embedded/list.ts)_
_See code: [packages/eas-cli/src/commands/update/embedded/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/embedded/list.ts)_
## `eas update:embedded:upload`
@@ -2724,7 +2727,7 @@ EXAMPLES
$ eas update:embedded:upload --platform android --bundle android/app/src/main/assets/index.android.bundle --manifest android/app/src/main/assets/app.manifest --channel production --build-id <BUILD-ID>
```
_See code: [packages/eas-cli/src/commands/update/embedded/upload.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/embedded/upload.ts)_
_See code: [packages/eas-cli/src/commands/update/embedded/upload.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/embedded/upload.ts)_
## `eas update:embedded:view ID`
@@ -2744,7 +2747,7 @@ DESCRIPTION
view details of an embedded update registered with EAS Update
```
_See code: [packages/eas-cli/src/commands/update/embedded/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/embedded/view.ts)_
_See code: [packages/eas-cli/src/commands/update/embedded/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/embedded/view.ts)_
## `eas update:insights GROUPID`
@@ -2771,7 +2774,7 @@ DESCRIPTION
display launch, crash, unique-user, and size insights for an update group
```
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/insights.ts)_
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/insights.ts)_
## `eas update:list`
@@ -2798,7 +2801,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/list.ts)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -2836,7 +2839,7 @@ DESCRIPTION
roll back to an existing update
```
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:revert-update-rollout`
@@ -2864,7 +2867,7 @@ DESCRIPTION
revert a rollout update for a project
```
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
## `eas update:roll-back-to-embedded`
@@ -2894,7 +2897,7 @@ DESCRIPTION
roll back to the embedded update
```
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback [GROUPID]`
@@ -2926,7 +2929,7 @@ DESCRIPTION
roll back to an embedded update or an existing update
```
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/rollback.ts)_
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -2950,7 +2953,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/update/view.ts)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas upload`
@@ -2971,7 +2974,7 @@ DESCRIPTION
upload a local build and generate a sharable link
```
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/upload.ts)_
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/upload.ts)_
## `eas webhook:create`
@@ -2993,7 +2996,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -3013,7 +3016,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -3032,7 +3035,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -3055,7 +3058,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -3072,7 +3075,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
@@ -3196,7 +3199,7 @@ DESCRIPTION
to cancel.
```
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
## `eas workflow:create [NAME]`
@@ -3218,7 +3221,7 @@ DESCRIPTION
create a new workflow configuration YAML file
```
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/create.ts)_
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/create.ts)_
## `eas workflow:logs [ID]`
@@ -3242,7 +3245,7 @@ DESCRIPTION
If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.
```
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/logs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/logs.ts)_
## `eas workflow:run FILE`
@@ -3282,7 +3285,7 @@ FLAG DESCRIPTIONS
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
```
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/run.ts)_
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/run.ts)_
## `eas workflow:runs`
@@ -3304,7 +3307,7 @@ DESCRIPTION
list recent workflow runs for this project, with their IDs, statuses, and timestamps
```
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/runs.ts)_
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/runs.ts)_
## `eas workflow:status [WORKFLOW_RUN_ID]`
@@ -3332,7 +3335,7 @@ FLAG DESCRIPTIONS
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
```
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/status.ts)_
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/status.ts)_
## `eas workflow:validate PATH`
@@ -3352,7 +3355,7 @@ DESCRIPTION
validate a workflow configuration yaml file
```
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/validate.ts)_
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/validate.ts)_
## `eas workflow:view [ID]`
@@ -3374,5 +3377,5 @@ DESCRIPTION
workflow runs for the current project.
```
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v21.3.0/packages/eas-cli/src/commands/workflow/view.ts)_
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v21.4.0/packages/eas-cli/src/commands/workflow/view.ts)_
<!-- commandsstop -->
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "eas-cli",
"version": "21.3.0",
"version": "21.4.0",
"description": "EAS command line tool",
"keywords": [
"cli",
@@ -48,8 +48,8 @@
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "55.0.10",
"@expo/config-plugins": "55.0.7",
"@expo/eas-build-job": "21.3.0",
"@expo/eas-json": "21.3.0",
"@expo/eas-build-job": "21.4.0",
"@expo/eas-json": "21.4.0",
"@expo/env": "^1.0.0",
"@expo/json-file": "8.3.3",
"@expo/logger": "21.0.0",
@@ -64,7 +64,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "21.3.0",
"@expo/steps": "21.4.0",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^4.8.3",
"@oclif/plugin-autocomplete": "^3.2.40",
@@ -133,6 +133,17 @@ describe(ObserveEvents, () => {
expect(options.eventName).toBeUndefined();
});
it('routes to fetchObserveCustomEventsAsync with the session filter when --session-id is set with no positional arg', async () => {
const command = createCommand(['--session-id', 'session-xyz']);
await command.runAsync();
expect(mockFetchObserveCustomEventsAsync).toHaveBeenCalledTimes(1);
expect(mockCustomEventNamesAsync).not.toHaveBeenCalled();
const options = mockFetchObserveCustomEventsAsync.mock.calls[0][2];
expect(options.eventName).toBeUndefined();
expect(options.sessionId).toBe('session-xyz');
});
it('throws when both an event name argument and --all-events are provided', async () => {
const command = createCommand(['my_event', '--all-events']);
await expect(command.runAsync()).rejects.toThrow(
@@ -55,7 +55,8 @@ export default class ObserveEvents extends EasCommand {
...ObserveAppVersionFlag,
...ObserveUpdateIdFlag,
'session-id': Flags.string({
description: 'Filter by session ID',
description:
'Filter by session ID. When no event name is given, lists the events in the session instead of the event-name summary.',
}),
'all-events': Flags.boolean({
description:
@@ -101,7 +102,10 @@ export default class ObserveEvents extends EasCommand {
const platform = appObservePlatformFromFlag(flags.platform);
if (!args.eventName && !flags['all-events']) {
// A session ID narrows to a single session, so show that session's events
// (like --all-events) instead of the account-wide name+count summary, which
// has no session filter.
if (!args.eventName && !flags['all-events'] && !flags['session-id']) {
const { names, isTruncated } = await withObservePlanGateHandlingAsync(() =>
ObserveQuery.customEventNamesAsync(graphqlClient, {
appId: projectId,
@@ -0,0 +1,353 @@
import { Config } from '@oclif/core';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import {
DeviceRunSessionEventsByIdQuery,
DeviceRunSessionStatus,
} from '../../../graphql/generated';
import { DeviceRunSessionQuery } from '../../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../../log';
import { loadSimulatorEnvAsync } from '../../../simulator/env';
import {
type DeviceRunSessionEvent,
downloadDeviceRunSessionEventsAsync,
formatDeviceRunSessionEvent,
projectDeviceRunSessionEventsForDisplay,
} from '../../../simulator/events';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import { sleepAsync } from '../../../utils/promise';
import SimulatorEvents from '../events';
jest.mock('../../../graphql/queries/DeviceRunSessionQuery');
jest.mock('../../../log');
jest.mock('../../../simulator/env', () => ({
...jest.requireActual('../../../simulator/env'),
loadSimulatorEnvAsync: jest.fn(),
}));
jest.mock('../../../simulator/events');
jest.mock('../../../utils/json');
jest.mock('../../../utils/promise');
const mockEventsByIdAsync = jest.mocked(DeviceRunSessionQuery.eventsByIdAsync);
const mockDownloadEventsAsync = jest.mocked(downloadDeviceRunSessionEventsAsync);
const mockFormatEvent = jest.mocked(formatDeviceRunSessionEvent);
const mockLoadSimulatorEnvAsync = jest.mocked(loadSimulatorEnvAsync);
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
const mockProjectEvents = jest.mocked(projectDeviceRunSessionEventsForDisplay);
const mockSleepAsync = jest.mocked(sleepAsync);
const mockDebug = jest.mocked(Log.debug);
const mockLog = jest.mocked(Log.log);
function getMockOclifConfig(): Config {
const config = new Config({ root: __dirname });
config.runHook = async () => ({ failures: [], successes: [] });
return config;
}
describe(SimulatorEvents, () => {
const graphqlClient = {} as ExpoGraphqlClient;
const projectDir = '/test/project';
beforeEach(() => {
jest.clearAllMocks();
mockLoadSimulatorEnvAsync.mockResolvedValue();
mockProjectEvents.mockImplementation(events => events);
mockFormatEvent.mockImplementation(event => `formatted:${event.eventId}`);
mockSleepAsync.mockResolvedValue();
});
it('prints a JSON snapshot for the requested session', async () => {
const session: DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId'] = {
id: 'session-id',
status: DeviceRunSessionStatus.InProgress,
artifacts: [
{
id: 'event-artifact-id',
downloadUrl: 'https://example.test/events',
metadata: { __eas_type: 'session-events' },
},
],
};
const events = [
{
v: 1 as const,
eventId: 'event-id',
ts: '2026-07-10T12:00:00.000Z',
producer: 'agent-device',
type: 'operation.started',
summary: 'Started tap',
},
];
mockEventsByIdAsync.mockResolvedValue(session);
mockDownloadEventsAsync.mockResolvedValue(events);
const command = new SimulatorEvents(['--id', 'session-id', '--json'], getMockOclifConfig());
// @ts-expect-error getContextAsync is protected
jest.spyOn(command, 'getContextAsync').mockResolvedValue({
loggedIn: { graphqlClient },
projectDir,
});
await command.runAsync();
expect(mockEnableJsonOutput).toHaveBeenCalled();
expect(mockLoadSimulatorEnvAsync).toHaveBeenCalledWith(projectDir);
expect(mockEventsByIdAsync).toHaveBeenCalledWith(graphqlClient, 'session-id');
expect(mockDownloadEventsAsync).toHaveBeenCalledWith('https://example.test/events');
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({
deviceRunSessionId: 'session-id',
events,
});
expect(mockProjectEvents).not.toHaveBeenCalled();
});
it('prints an empty snapshot when the session has no event artifact', async () => {
mockEventsByIdAsync.mockResolvedValue({
id: 'session-id',
status: DeviceRunSessionStatus.Stopped,
artifacts: [],
});
const command = new SimulatorEvents(['--id', 'session-id', '--json'], getMockOclifConfig());
// @ts-expect-error getContextAsync is protected
jest.spyOn(command, 'getContextAsync').mockResolvedValue({
loggedIn: { graphqlClient },
projectDir,
});
await command.runAsync();
expect(mockDownloadEventsAsync).not.toHaveBeenCalled();
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({
deviceRunSessionId: 'session-id',
events: [],
});
});
it('prints a condensed text snapshot', async () => {
const session = createSession(DeviceRunSessionStatus.Stopped);
const events = [createEvent()];
mockEventsByIdAsync.mockResolvedValue(session);
mockDownloadEventsAsync.mockResolvedValue(events);
const command = createCommand(['--id', 'session-id']);
await command.runAsync();
expect(mockProjectEvents).toHaveBeenCalledWith(events, {
includeIncompleteOperations: true,
});
expect(mockFormatEvent).toHaveBeenCalledWith(events[0]);
expect(mockLog).toHaveBeenCalledWith('formatted:event-id');
});
it('refreshes twice after a followed running session stops before flushing incomplete events', async () => {
const events = [createEvent()];
mockEventsByIdAsync
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.InProgress))
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.Stopped))
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.Stopped))
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.Stopped));
mockDownloadEventsAsync.mockResolvedValue(events);
mockProjectEvents.mockImplementation((projectedEvents, options) =>
options?.includeIncompleteOperations ? projectedEvents : []
);
const command = createCommand(['--id', 'session-id', '--follow']);
await command.runAsync();
expect(mockEventsByIdAsync).toHaveBeenCalledTimes(4);
expect(mockDownloadEventsAsync).toHaveBeenCalledTimes(4);
expect(mockSleepAsync).toHaveBeenCalledTimes(3);
expect(
mockProjectEvents.mock.calls.map(([, options]) => options?.includeIncompleteOperations)
).toEqual([false, false, false, true]);
expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog).toHaveBeenCalledWith('formatted:event-id');
});
it('wakes from polling and flushes incomplete events when follow mode is interrupted', async () => {
const existingInterruptListeners = new Set(process.listeners('SIGINT'));
mockEventsByIdAsync.mockResolvedValue(createSession(DeviceRunSessionStatus.InProgress));
const events = [createEvent()];
mockDownloadEventsAsync.mockResolvedValue(events);
mockProjectEvents.mockImplementation((projectedEvents, options) =>
options?.includeIncompleteOperations ? projectedEvents : []
);
let notifySleepStarted: () => void;
const sleepStarted = new Promise<void>(resolve => {
notifySleepStarted = resolve;
});
mockSleepAsync.mockImplementationOnce(() => {
notifySleepStarted();
return new Promise<void>(() => {});
});
const command = createCommand(['--id', 'session-id', '--follow']);
const commandPromise = command.runAsync();
await sleepStarted;
const interruptHandler = process
.listeners('SIGINT')
.find(listener => !existingInterruptListeners.has(listener));
expect(interruptHandler).toBeDefined();
interruptHandler?.('SIGINT');
await commandPromise;
expect(mockEventsByIdAsync).toHaveBeenCalledTimes(1);
expect(
mockProjectEvents.mock.calls.map(([, options]) => options?.includeIncompleteOperations)
).toEqual([false, true]);
expect(mockLog).toHaveBeenCalledWith('formatted:event-id');
expect(process.listeners('SIGINT')).toEqual([...existingInterruptListeners]);
});
it('force exits on a second interrupt', async () => {
const existingInterruptListeners = new Set(process.listeners('SIGINT'));
mockEventsByIdAsync.mockResolvedValue(createSession(DeviceRunSessionStatus.InProgress));
mockDownloadEventsAsync.mockResolvedValue([]);
let notifySleepStarted: () => void;
const sleepStarted = new Promise<void>(resolve => {
notifySleepStarted = resolve;
});
mockSleepAsync.mockImplementationOnce(() => {
notifySleepStarted();
return new Promise<void>(() => {});
});
const exitSpy = jest
.spyOn(process, 'exit')
.mockImplementation((code?: string | number | null): never => {
throw new Error(`process.exit(${code})`);
});
const command = createCommand(['--id', 'session-id', '--follow']);
try {
const commandPromise = command.runAsync();
await sleepStarted;
const interruptHandler = process
.listeners('SIGINT')
.find(listener => !existingInterruptListeners.has(listener));
expect(interruptHandler).toBeDefined();
interruptHandler?.('SIGINT');
expect(() => interruptHandler?.('SIGINT')).toThrow('process.exit(130)');
await commandPromise;
expect(exitSpy).toHaveBeenCalledWith(130);
expect(process.listeners('SIGINT')).toEqual([...existingInterruptListeners]);
} finally {
exitSpy.mockRestore();
}
});
it('retries transient query and artifact download errors in follow mode', async () => {
const events = [createEvent()];
mockEventsByIdAsync
.mockRejectedValueOnce(new Error('query unavailable'))
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.InProgress))
.mockResolvedValueOnce(createSession(DeviceRunSessionStatus.Stopped));
mockDownloadEventsAsync
.mockRejectedValueOnce(new Error('artifact unavailable'))
.mockResolvedValueOnce(events);
const command = createCommand(['--id', 'session-id', '--follow']);
await command.runAsync();
expect(mockEventsByIdAsync).toHaveBeenCalledTimes(3);
expect(mockDownloadEventsAsync).toHaveBeenCalledTimes(2);
expect(mockSleepAsync).toHaveBeenCalledTimes(2);
expect(mockDebug).toHaveBeenNthCalledWith(
1,
'Failed to poll simulator session events: query unavailable'
);
expect(mockDebug).toHaveBeenNthCalledWith(
2,
'Failed to poll simulator session events: artifact unavailable'
);
expect(mockLog).toHaveBeenCalledWith('formatted:event-id');
});
it('does not retry query errors outside follow mode', async () => {
const existingInterruptListeners = process.listeners('SIGINT');
mockEventsByIdAsync.mockRejectedValue(new Error('query unavailable'));
const command = createCommand(['--id', 'session-id']);
await expect(command.runAsync()).rejects.toThrow('query unavailable');
expect(mockSleepAsync).not.toHaveBeenCalled();
expect(mockDebug).not.toHaveBeenCalled();
expect(process.listeners('SIGINT')).toEqual(existingInterruptListeners);
});
it('does not register an interrupt handler outside follow mode', async () => {
const existingInterruptListeners = process.listeners('SIGINT');
let notifyQueryStarted: () => void;
const queryStarted = new Promise<void>(resolve => {
notifyQueryStarted = resolve;
});
let resolveSession: (
session: DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId']
) => void = () => {
throw new Error('Session promise was not initialized.');
};
const pendingSession = new Promise<
DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId']
>(resolve => {
resolveSession = resolve;
});
mockEventsByIdAsync.mockImplementationOnce(() => {
notifyQueryStarted();
return pendingSession;
});
mockDownloadEventsAsync.mockResolvedValue([]);
const command = createCommand(['--id', 'session-id']);
const commandPromise = command.runAsync();
await queryStarted;
expect(process.listeners('SIGINT')).toEqual(existingInterruptListeners);
resolveSession(createSession(DeviceRunSessionStatus.Stopped));
await commandPromise;
});
it('rejects combining JSON and follow output', async () => {
const command = new SimulatorEvents(
['--id', 'session-id', '--json', '--follow'],
getMockOclifConfig()
);
await expect(command.runAsync()).rejects.toThrow('Use either --json or --follow, not both.');
});
});
function createCommand(args: string[]): SimulatorEvents {
const command = new SimulatorEvents(args, getMockOclifConfig());
// @ts-expect-error getContextAsync is protected
jest.spyOn(command, 'getContextAsync').mockResolvedValue({
loggedIn: { graphqlClient: {} as ExpoGraphqlClient },
projectDir: '/test/project',
});
return command;
}
function createSession(
status: DeviceRunSessionStatus
): DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId'] {
return {
id: 'session-id',
status,
artifacts: [
{
id: 'event-artifact-id',
downloadUrl: 'https://example.test/events',
metadata: { __eas_type: 'session-events' },
},
],
};
}
function createEvent(): DeviceRunSessionEvent {
return {
v: 1 as const,
eventId: 'event-id',
ts: '2026-07-10T12:00:00.000Z',
producer: 'agent-device',
type: 'operation.started',
summary: 'Started tap',
};
}
@@ -9,6 +9,7 @@ import {
JobRunStatus,
} from '../../../graphql/generated';
import { DeviceRunSessionQuery } from '../../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../../log';
import {
EAS_SIMULATOR_SESSION_ID,
SIMULATOR_DOTENV_FILE_NAME,
@@ -46,6 +47,7 @@ const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
function makeDeviceRunSession(overrides: Partial<DeviceRunSessionById> = {}): DeviceRunSessionById {
return {
id: 'session-123',
name: null,
status: DeviceRunSessionStatus.InProgress,
type: DeviceRunSessionType.AgentDevice,
platform: AppPlatform.Ios,
@@ -134,6 +136,7 @@ describe(SimulatorGet, () => {
expect(mockByIdAsync).toHaveBeenCalledWith(graphqlClient, 'session-123');
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({
id: 'session-123',
name: undefined,
type: 'agent-device',
status: DeviceRunSessionStatus.InProgress,
platform: AppPlatform.Ios,
@@ -148,6 +151,26 @@ describe(SimulatorGet, () => {
});
});
it('includes the session name in JSON and human output when the session is named', async () => {
mockByIdAsync.mockResolvedValue(makeDeviceRunSession({ name: 'Checkout regression' }));
const { command } = createCommand(['--id', 'session-123', '--json']);
await command.runAsync();
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith(
expect.objectContaining({ name: 'Checkout regression' })
);
jest.clearAllMocks();
mockLoadSimulatorEnvironmentVariablesAsync.mockResolvedValue();
mockByIdAsync.mockResolvedValue(makeDeviceRunSession({ name: 'Checkout regression' }));
const { command: humanCommand } = createCommand(['--id', 'session-123']);
await humanCommand.runAsync();
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Checkout regression'));
});
it(`uses ${EAS_SIMULATOR_SESSION_ID} from simulator env when --id is not passed`, async () => {
const previousDeviceRunSessionId = process.env[EAS_SIMULATOR_SESSION_ID];
const session = makeDeviceRunSession({ id: 'session-from-env' });
@@ -9,6 +9,7 @@ import {
JobRunStatus,
} from '../../../graphql/generated';
import { DeviceRunSessionQuery } from '../../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import SimulatorList from '../list';
@@ -40,6 +41,7 @@ const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
function makeSession(overrides: Partial<DeviceRunSessionNode> = {}): DeviceRunSessionNode {
return {
id: 'session-123',
name: null,
status: DeviceRunSessionStatus.InProgress,
type: DeviceRunSessionType.AgentDevice,
platform: AppPlatform.Ios,
@@ -129,6 +131,7 @@ describe(SimulatorList, () => {
sessions: [
{
id: 'session-123',
name: undefined,
type: 'agent-device',
status: DeviceRunSessionStatus.InProgress,
platform: AppPlatform.Ios,
@@ -180,6 +183,26 @@ describe(SimulatorList, () => {
});
});
it('prints a name row for every session, named or not', async () => {
mockListByAppIdAsync.mockResolvedValue(
makeConnection([
makeSession({ id: 'session-1', name: 'Checkout regression' }),
makeSession({ id: 'session-2' }),
])
);
const { command } = createCommand([]);
await command.runAsync();
const printed = jest
.mocked(Log.log)
.mock.calls.map(([entry]) => String(entry))
.join('\n');
expect(printed).toContain('Name: Checkout regression');
expect(printed).toContain('Name: null');
expect(printed.match(/Name:/g)).toHaveLength(2);
});
it('runs non-interactively without --json', async () => {
const session = makeSession({ id: 'session-1', turtleJobRun: null });
mockListByAppIdAsync.mockResolvedValue(makeConnection([session]));
@@ -99,6 +99,7 @@ function makeCreatedDeviceRunSession(
function makeDeviceRunSession(overrides: Partial<DeviceRunSessionById> = {}): DeviceRunSessionById {
return {
id: 'session-123',
name: null,
status: DeviceRunSessionStatus.InProgress,
type: DeviceRunSessionType.AgentDevice,
platform: AppPlatform.Ios,
@@ -189,6 +190,7 @@ describe(SimulatorStart, () => {
});
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
name: undefined,
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -278,6 +280,7 @@ describe(SimulatorStart, () => {
);
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
name: undefined,
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -297,6 +300,7 @@ describe(SimulatorStart, () => {
);
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
name: undefined,
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -325,6 +329,48 @@ describe(SimulatorStart, () => {
expect(mockResetSimulatorEnvAsync).toHaveBeenCalledWith(projectDir);
});
it('forwards --name to the create mutation', async () => {
const { command } = createCommand([
'--platform',
'ios',
'--non-interactive',
'--name',
'Checkout regression',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ name: 'Checkout regression' })
);
});
it('trims --name before sending it', async () => {
const { command } = createCommand([
'--platform',
'ios',
'--non-interactive',
'--name',
' Checkout regression ',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ name: 'Checkout regression' })
);
});
it('omits a blank --name instead of letting the server reject it', async () => {
const { command } = createCommand(['--platform', 'ios', '--non-interactive', '--name', ' ']);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ name: undefined })
);
});
it('prompts to select the platform when --platform is omitted', async () => {
mockPromptAsync.mockResolvedValueOnce({ selectedPlatform: AppPlatform.Android });
mockByIdAsync
@@ -0,0 +1,180 @@
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand';
import {
EasNonInteractiveAndJsonFlags,
resolveNonInteractiveAndJsonFlags,
} from '../../commandUtils/flags';
import {
type DeviceRunSessionEventsByIdQuery,
DeviceRunSessionStatus,
} from '../../graphql/generated';
import { DeviceRunSessionQuery } from '../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../log';
import {
EAS_SIMULATOR_SESSION_ID,
SIMULATOR_DOTENV_FILE_NAME,
loadSimulatorEnvAsync,
} from '../../simulator/env';
import {
type DeviceRunSessionEvent,
downloadDeviceRunSessionEventsAsync,
formatDeviceRunSessionEvent,
projectDeviceRunSessionEventsForDisplay,
} from '../../simulator/events';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { sleepAsync } from '../../utils/promise';
const POLL_INTERVAL_MS = 5_000;
const POST_STOP_REFRESH_COUNT = 2;
export default class SimulatorEvents extends EasCommand {
static override hidden = true;
static override description =
'[EXPERIMENTAL] show activity events from a remote simulator session';
static override flags = {
id: Flags.string({
description: `Simulator session ID. Defaults to ${SIMULATOR_DOTENV_FILE_NAME}.`,
}),
follow: Flags.boolean({
char: 'f',
description: 'Keep watching for new events until the session ends.',
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.ProjectDir,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(SimulatorEvents);
const { json: jsonFlag, nonInteractive } = resolveNonInteractiveAndJsonFlags(flags);
if (jsonFlag && flags.follow) {
throw new Error('Use either --json or --follow, not both.');
}
if (jsonFlag) {
enableJsonOutput();
}
const {
projectDir,
loggedIn: { graphqlClient },
} = await this.getContextAsync(SimulatorEvents, { nonInteractive });
await loadSimulatorEnvAsync(projectDir);
const deviceRunSessionId = flags.id ?? process.env[EAS_SIMULATOR_SESSION_ID];
if (!deviceRunSessionId) {
throw new Error(
`No simulator session ID provided. Pass --id, or run \`eas simulator:start\` first to write ${SIMULATOR_DOTENV_FILE_NAME}.`
);
}
const printedEventIds = new Set<string>();
let observedRunningSession = false;
let remainingPostStopRefreshes = 0;
let latestEvents: DeviceRunSessionEvent[] = [];
const abortController = new AbortController();
const { signal } = abortController;
const abortPromise = new Promise<void>(resolve => {
signal.addEventListener(
'abort',
() => {
resolve();
},
{ once: true }
);
});
const interruptHandler = (): void => {
if (signal.aborted) {
process.exit(130);
}
abortController.abort();
};
if (flags.follow) {
process.on('SIGINT', interruptHandler);
}
try {
while (!signal.aborted) {
let session: DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId'];
let events: DeviceRunSessionEvent[];
try {
session = await DeviceRunSessionQuery.eventsByIdAsync(graphqlClient, deviceRunSessionId);
const eventArtifact = session.artifacts.find(
artifact => artifact.metadata?.__eas_type === 'session-events'
);
events = eventArtifact
? await downloadDeviceRunSessionEventsAsync(eventArtifact.downloadUrl)
: [];
} catch (err) {
if (!flags.follow) {
throw err;
}
Log.debug(
`Failed to poll simulator session events: ${
err instanceof Error ? err.message : String(err)
}`
);
await Promise.race([sleepAsync(POLL_INTERVAL_MS), abortPromise]);
continue;
}
latestEvents = events;
if (jsonFlag) {
printJsonOnlyOutput({ deviceRunSessionId, events });
return;
}
const isRunning =
session.status === DeviceRunSessionStatus.New ||
session.status === DeviceRunSessionStatus.InProgress;
if (isRunning) {
observedRunningSession = true;
remainingPostStopRefreshes = 0;
} else if (observedRunningSession) {
observedRunningSession = false;
remainingPostStopRefreshes = POST_STOP_REFRESH_COUNT;
}
const shouldRefreshAgain =
flags.follow && !signal.aborted && (isRunning || remainingPostStopRefreshes > 0);
printNewEvents(events, printedEventIds, !shouldRefreshAgain);
if (!shouldRefreshAgain) {
if (events.length === 0) {
Log.log('No simulator session activity has been recorded.');
}
return;
}
if (!isRunning) {
remainingPostStopRefreshes -= 1;
}
await Promise.race([sleepAsync(POLL_INTERVAL_MS), abortPromise]);
}
printNewEvents(latestEvents, printedEventIds, true);
} finally {
if (flags.follow) {
process.removeListener('SIGINT', interruptHandler);
}
}
}
}
function printNewEvents(
events: DeviceRunSessionEvent[],
printedEventIds: Set<string>,
includeIncompleteOperations: boolean
): void {
const displayEvents = projectDeviceRunSessionEventsForDisplay(events, {
includeIncompleteOperations,
});
for (const event of displayEvents) {
if (printedEventIds.has(event.eventId)) {
continue;
}
Log.log(formatDeviceRunSessionEvent(event));
printedEventIds.add(event.eventId);
}
}
@@ -85,6 +85,7 @@ export default class SimulatorGet extends EasCommand {
if (jsonFlag) {
printJsonOnlyOutput({
id: session.id,
name: session.name ?? undefined,
type: deviceRunSessionTypeToFlagValue(session.type),
status: session.status,
platform: session.platform,
@@ -120,6 +121,7 @@ export default class SimulatorGet extends EasCommand {
function formatSessionFields(session: DeviceRunSessionById, deviceRunSessionUrl: string): string {
return formatFields([
{ label: 'ID', value: session.id },
{ label: 'Name', value: formatNullable(session.name) },
{ label: 'Type', value: session.type },
{ label: 'Status', value: session.status },
{ label: 'Platform', value: session.platform },
@@ -131,6 +131,7 @@ export default class SimulatorList extends EasCommand {
printJsonOnlyOutput({
sessions: sessions.map(session => ({
id: session.id,
name: session.name ?? undefined,
type: deviceRunSessionTypeToFlagValue(session.type),
status: session.status,
platform: session.platform,
@@ -163,6 +164,7 @@ export default class SimulatorList extends EasCommand {
);
const lines = [
`ID: ${session.id}`,
`Name: ${session.name ?? 'null'}`,
`Type: ${session.type}`,
`Status: ${session.status}`,
`Platform: ${session.platform}`,
@@ -61,6 +61,10 @@ export default class SimulatorStart extends EasCommand {
description: 'Device platform',
options: PLATFORM_FLAG_VALUES,
})(),
name: Flags.string({
description:
'Human-readable name for the simulator session, shown in eas simulator:list and on expo.dev. Defaults to unnamed.',
}),
type: Flags.option({
description: 'Type of simulator session to create',
options: Object.values(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES),
@@ -111,6 +115,10 @@ export default class SimulatorStart extends EasCommand {
nonInteractive,
});
// The server rejects blank names, so trim here and treat a whitespace-only
// --name as if it had been omitted rather than surfacing a validation error.
const name = flags.name?.trim() || undefined;
await loadSimulatorEnvAsync(projectDir);
const existingDeviceRunSessionId = process.env[EAS_SIMULATOR_SESSION_ID];
if (existingDeviceRunSessionId && !flags.force) {
@@ -136,6 +144,7 @@ export default class SimulatorStart extends EasCommand {
try {
const session = await DeviceRunSessionMutation.createDeviceRunSessionAsync(graphqlClient, {
appId: projectId,
name,
platform,
type: DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE[flags.type],
packageVersion: flags['package-version'],
@@ -224,6 +233,7 @@ export default class SimulatorStart extends EasCommand {
if (jsonFlag) {
printJsonOnlyOutput({
id: deviceRunSessionId,
name,
type: flags.type,
deviceRunSessionUrl,
remoteConfig,
+171 -2
View File
@@ -2149,6 +2149,12 @@ export type AppObserve = {
errorTimeSeries: AppObserveErrorTimeSeries;
events: AppObserveEventsConnection;
navigationRoutes: AppObserveNavigationRoutesConnection;
/** Active users and sessions for the Overview engagement band, across all versions. */
overviewEngagement: AppObserveOverviewEngagement;
/** Per-update comparison within one app version: the embedded bundle plus each OTA update. */
overviewUpdateComparison: AppObserveOverviewUpdateComparison;
/** Per-version, per-platform metric summaries for the Overview version-comparison matrix. */
overviewVersionComparison: AppObserveOverviewVersionComparison;
timeSeries: AppObserveTimeSeries;
totalEventCount: Scalars['Int']['output'];
/**
@@ -2245,6 +2251,21 @@ export type AppObserveNavigationRoutesArgs = {
};
export type AppObserveOverviewEngagementArgs = {
input: AppObserveOverviewEngagementInput;
};
export type AppObserveOverviewUpdateComparisonArgs = {
input: AppObserveOverviewUpdateComparisonInput;
};
export type AppObserveOverviewVersionComparisonArgs = {
input: AppObserveOverviewVersionComparisonInput;
};
export type AppObserveTimeSeriesArgs = {
input: AppObserveTimeSeriesInput;
};
@@ -2810,6 +2831,132 @@ export type AppObserveNavigationStat = {
p90?: Maybe<Scalars['Float']['output']>;
};
export type AppObserveOverviewEngagement = {
__typename?: 'AppObserveOverviewEngagement';
activeUsers: AppObserveOverviewEngagementStat;
sessions: AppObserveOverviewEngagementStat;
};
export type AppObserveOverviewEngagementBucket = {
__typename?: 'AppObserveOverviewEngagementBucket';
bucketStart: Scalars['DateTime']['output'];
count: Scalars['Int']['output'];
};
/** Engagement-band filters. No release filters: the band always spans all versions. */
export type AppObserveOverviewEngagementInput = {
/** Series bucket size. Defaults to one day. */
bucketIntervalMinutes?: InputMaybe<Scalars['Int']['input']>;
endTime: Scalars['DateTime']['input'];
environment?: InputMaybe<Scalars['String']['input']>;
platform?: InputMaybe<AppObservePlatform>;
startTime: Scalars['DateTime']['input'];
};
export type AppObserveOverviewEngagementStat = {
__typename?: 'AppObserveOverviewEngagementStat';
previousPeriodTotal: Scalars['Int']['output'];
/** Per-bucket approximate uniques; buckets do not sum to `total`. */
series: Array<AppObserveOverviewEngagementBucket>;
total: Scalars['Int']['output'];
};
export type AppObserveOverviewStability = {
__typename?: 'AppObserveOverviewStability';
affectedUsers: Scalars['Int']['output'];
/** Fraction (0..1) of active sessions without a fatal error. */
crashFreeSessions: Scalars['Float']['output'];
/** Fraction (0..1) of active users without a fatal error. */
crashFreeUsers: Scalars['Float']['output'];
totalErrors: Scalars['Int']['output'];
};
export type AppObserveOverviewUpdate = {
__typename?: 'AppObserveOverviewUpdate';
/** Null for the embedded bundle. */
appUpdateId?: Maybe<Scalars['ID']['output']>;
/** Downloads recorded in range; null when none were recorded (and always for the embedded bundle). */
downloadCount?: Maybe<Scalars['Int']['output']>;
eventCount: Scalars['Int']['output'];
firstSeenAt: Scalars['DateTime']['output'];
isEmbedded: Scalars['Boolean']['output'];
medianDownloadTime?: Maybe<Scalars['Float']['output']>;
/** EAS update message; null for the embedded bundle or when the update row no longer exists. */
message?: Maybe<Scalars['String']['output']>;
/** One entry per metric with data in range, all platforms combined. */
metrics: Array<AppObserveOverviewUpdateMetric>;
/** When the update was published; null for the embedded bundle. */
publishedAt?: Maybe<Scalars['DateTime']['output']>;
stability: AppObserveOverviewStability;
uniqueUserCount: Scalars['Int']['output'];
};
export type AppObserveOverviewUpdateComparison = {
__typename?: 'AppObserveOverviewUpdateComparison';
/** Embedded bundle first, then OTA updates oldest first. Only updates with runtime data appear. */
updates: Array<AppObserveOverviewUpdate>;
};
export type AppObserveOverviewUpdateComparisonInput = {
appVersion: Scalars['String']['input'];
endTime: Scalars['DateTime']['input'];
environment?: InputMaybe<Scalars['String']['input']>;
/** Metric names to aggregate. Defaults to all metrics available on the account's plan. */
metricNames?: InputMaybe<Array<Scalars['String']['input']>>;
startTime: Scalars['DateTime']['input'];
};
export type AppObserveOverviewUpdateMetric = {
__typename?: 'AppObserveOverviewUpdateMetric';
eventCount: Scalars['Int']['output'];
metricName: Scalars['String']['output'];
statistics: AppObserveVersionMarkerStatistics;
};
export type AppObserveOverviewVersion = {
__typename?: 'AppObserveOverviewVersion';
appVersion: Scalars['String']['output'];
firstSeenAt: Scalars['DateTime']['output'];
/** One entry per (metric, platform) with data in range; iOS includes tvOS and iPadOS. */
metrics: Array<AppObserveOverviewVersionMetric>;
/** Error stats for this version, all platforms combined. */
stability: AppObserveOverviewStability;
uniqueUserCount: Scalars['Int']['output'];
};
export type AppObserveOverviewVersionComparison = {
__typename?: 'AppObserveOverviewVersionComparison';
/** Every version with data in range, highest version first, for the version picker. */
allVersions: Array<AppObserveOverviewVersionSummary>;
/** Compared versions, lowest version first. Requested versions with no data are omitted. */
versions: Array<AppObserveOverviewVersion>;
};
export type AppObserveOverviewVersionComparisonInput = {
/** Versions to compare (max 10, any order). Defaults to the three highest versions. */
appVersions?: InputMaybe<Array<Scalars['String']['input']>>;
endTime: Scalars['DateTime']['input'];
environment?: InputMaybe<Scalars['String']['input']>;
/** Metric names to aggregate. Defaults to all metrics available on the account's plan. */
metricNames?: InputMaybe<Array<Scalars['String']['input']>>;
startTime: Scalars['DateTime']['input'];
};
export type AppObserveOverviewVersionMetric = {
__typename?: 'AppObserveOverviewVersionMetric';
eventCount: Scalars['Int']['output'];
metricName: Scalars['String']['output'];
platform: AppObservePlatform;
statistics: AppObserveVersionMarkerStatistics;
};
export type AppObserveOverviewVersionSummary = {
__typename?: 'AppObserveOverviewVersionSummary';
appVersion: Scalars['String']['output'];
firstSeenAt: Scalars['DateTime']['output'];
uniqueUserCount: Scalars['Int']['output'];
};
export enum AppObservePlatform {
Android = 'ANDROID',
Ios = 'IOS'
@@ -4933,6 +5080,11 @@ export type CreateDeviceRunSessionInput = {
* plans. If omitted, the default is derived based on the job run's priority.
*/
maxRunTimeMinutes?: InputMaybe<Scalars['Int']['input']>;
/**
* Human-readable label for the session, at most 255 characters. If omitted, the
* session is unnamed and clients fall back to identifying it by id.
*/
name?: InputMaybe<Scalars['String']['input']>;
/**
* The version of the package backing the device run session (e.g. "0.1.3-alpha.3").
* If omitted, consumers treat the session as pinned to "latest".
@@ -5689,6 +5841,11 @@ export type DeviceRunSession = {
finishedAt?: Maybe<Scalars['DateTime']['output']>;
id: Scalars['ID']['output'];
initiatingActor?: Maybe<Actor>;
/**
* Human-readable label chosen by whoever started the session. Null when the
* session was started without one.
*/
name?: Maybe<Scalars['String']['output']>;
/**
* The version of the package backing the device run session. Null means the session is
* pinned to "latest" at the consumer side.
@@ -10746,6 +10903,11 @@ export type User = Actor & UserActor & {
lastName?: Maybe<Scalars['String']['output']>;
newEmailPendingVerification?: Maybe<Scalars['String']['output']>;
oAuthIdentities: Array<OAuthIdentity>;
/**
* The organization created during signup onboarding: the most recently
* created organization this user owns. Only resolves for the viewer.
*/
onboardingOrganization?: Maybe<Account>;
/** Registered passkey credentials */
passkeyCredentials: Array<UserPasskeyCredential>;
/** Pending UserInvitations for this user. Only resolves for the viewer. */
@@ -14415,12 +14577,19 @@ export type SimulatorAvailabilityQueryVariables = Exact<{
export type SimulatorAvailabilityQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, ownerAccount: { __typename?: 'Account', id: string, name: string, accountFeatureGates: any } } } };
export type DeviceRunSessionEventsByIdQueryVariables = Exact<{
deviceRunSessionId: Scalars['ID']['input'];
}>;
export type DeviceRunSessionEventsByIdQuery = { __typename?: 'RootQuery', deviceRunSessions: { __typename?: 'DeviceRunSessionQuery', byId: { __typename?: 'DeviceRunSession', id: string, status: DeviceRunSessionStatus, artifacts: Array<{ __typename?: 'DeviceRunSessionArtifact', id: string, downloadUrl: string, metadata?: any | null }> } } };
export type DeviceRunSessionByIdQueryVariables = Exact<{
deviceRunSessionId: Scalars['ID']['input'];
}>;
export type DeviceRunSessionByIdQuery = { __typename?: 'RootQuery', deviceRunSessions: { __typename?: 'DeviceRunSessionQuery', byId: { __typename?: 'DeviceRunSession', id: string, status: DeviceRunSessionStatus, type: DeviceRunSessionType, platform: AppPlatform, createdAt: any, startedAt?: any | null, finishedAt?: any | null, updatedAt: any, app: { __typename?: 'App', id: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, artifacts: Array<{ __typename?: 'DeviceRunSessionArtifact', id: string, name: string, filename: string, downloadUrl: string, fileSizeBytes?: number | null, metadata?: any | null, createdAt: any, updatedAt: any }>, remoteConfig?:
export type DeviceRunSessionByIdQuery = { __typename?: 'RootQuery', deviceRunSessions: { __typename?: 'DeviceRunSessionQuery', byId: { __typename?: 'DeviceRunSession', id: string, name?: string | null, status: DeviceRunSessionStatus, type: DeviceRunSessionType, platform: AppPlatform, createdAt: any, startedAt?: any | null, finishedAt?: any | null, updatedAt: any, app: { __typename?: 'App', id: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, artifacts: Array<{ __typename?: 'DeviceRunSessionArtifact', id: string, name: string, filename: string, downloadUrl: string, fileSizeBytes?: number | null, metadata?: any | null, createdAt: any, updatedAt: any }>, remoteConfig?:
| { __typename: 'AgentDeviceRunSessionRemoteConfig', agentDeviceRemoteSessionUrl: string, agentDeviceRemoteSessionToken: string, webPreviewUrl?: string | null }
| { __typename: 'ArgentRunSessionRemoteConfig', toolsUrl: string, toolsAuthToken?: string | null, webPreviewUrl?: string | null }
| { __typename: 'ServeSimRunSessionRemoteConfig', previewUrl: string }
@@ -14434,7 +14603,7 @@ export type DeviceRunSessionsByAppIdQueryVariables = Exact<{
}>;
export type DeviceRunSessionsByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, deviceRunSessionsPaginated: { __typename?: 'AppDeviceRunSessionsConnection', edges: Array<{ __typename?: 'AppDeviceRunSessionEdge', cursor: string, node: { __typename?: 'DeviceRunSession', id: string, status: DeviceRunSessionStatus, type: DeviceRunSessionType, platform: AppPlatform, createdAt: any, startedAt?: any | null, finishedAt?: any | null, app: { __typename?: 'App', id: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, turtleJobRun?: { __typename?: 'JobRun', id: string, status: JobRunStatus } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
export type DeviceRunSessionsByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, deviceRunSessionsPaginated: { __typename?: 'AppDeviceRunSessionsConnection', edges: Array<{ __typename?: 'AppDeviceRunSessionEdge', cursor: string, node: { __typename?: 'DeviceRunSession', id: string, name?: string | null, status: DeviceRunSessionStatus, type: DeviceRunSessionType, platform: AppPlatform, createdAt: any, startedAt?: any | null, finishedAt?: any | null, app: { __typename?: 'App', id: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, turtleJobRun?: { __typename?: 'JobRun', id: string, status: JobRunStatus } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
export type ViewEmbeddedUpdateByIdQueryVariables = Exact<{
embeddedUpdateId: Scalars['ID']['input'];
@@ -5,12 +5,43 @@ import { withErrorHandlingAsync } from '../client';
import {
DeviceRunSessionByIdQuery,
DeviceRunSessionByIdQueryVariables,
DeviceRunSessionEventsByIdQuery,
DeviceRunSessionEventsByIdQueryVariables,
DeviceRunSessionFilterInput,
DeviceRunSessionsByAppIdQuery,
DeviceRunSessionsByAppIdQueryVariables,
} from '../generated';
export const DeviceRunSessionQuery = {
async eventsByIdAsync(
graphqlClient: ExpoGraphqlClient,
deviceRunSessionId: string
): Promise<DeviceRunSessionEventsByIdQuery['deviceRunSessions']['byId']> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<DeviceRunSessionEventsByIdQuery, DeviceRunSessionEventsByIdQueryVariables>(
gql`
query DeviceRunSessionEventsByIdQuery($deviceRunSessionId: ID!) {
deviceRunSessions {
byId(deviceRunSessionId: $deviceRunSessionId) {
id
status
artifacts {
id
downloadUrl
metadata
}
}
}
}
`,
{ deviceRunSessionId },
{ requestPolicy: 'network-only' }
)
.toPromise()
);
return data.deviceRunSessions.byId;
},
async byIdAsync(
graphqlClient: ExpoGraphqlClient,
deviceRunSessionId: string
@@ -23,6 +54,7 @@ export const DeviceRunSessionQuery = {
deviceRunSessions {
byId(deviceRunSessionId: $deviceRunSessionId) {
id
name
status
type
platform
@@ -111,6 +143,7 @@ export const DeviceRunSessionQuery = {
cursor
node {
id
name
status
type
platform
@@ -0,0 +1,198 @@
import fetch, { RequestError, Response } from '../../fetch';
import {
type DeviceRunSessionEvent,
downloadDeviceRunSessionEventsAsync,
formatDeviceRunSessionEvent,
projectDeviceRunSessionEventsForDisplay,
} from '../events';
jest.mock('../../fetch', () => ({
__esModule: true,
...jest.requireActual('../../fetch'),
default: jest.fn(),
}));
describe(projectDeviceRunSessionEventsForDisplay, () => {
it('collapses completed operations and carries duration onto the recorded interaction', () => {
const events = [
createEventValue({
eventId: 'started',
type: 'operation.started',
operationId: 'operation-id',
summary: 'Started press',
}),
createEventValue({
eventId: 'interaction',
ts: '2026-07-10T12:00:01.000Z',
type: 'interaction.recorded',
operationId: 'operation-id',
summary: 'Tapped @e13',
}),
createEventValue({
eventId: 'completed',
ts: '2026-07-10T12:00:02.000Z',
type: 'operation.completed',
operationId: 'operation-id',
outcome: 'success',
durationMs: 123,
summary: 'Finished press',
}),
];
expect(projectDeviceRunSessionEventsForDisplay(events)).toEqual([
{
...events[1],
durationMs: 123,
outcome: 'success',
},
]);
});
it('preserves completed failures and successful operations without interactions', () => {
const successful = createEventValue({
eventId: 'successful',
type: 'operation.completed',
operationId: 'successful-operation',
outcome: 'success',
summary: 'Found 2 devices',
});
const failed = createEventValue({
eventId: 'failed',
ts: '2026-07-10T12:00:01.000Z',
type: 'operation.completed',
operationId: 'failed-operation',
outcome: 'failure',
summary: 'Failed install: INVALID_ARGS',
});
expect(projectDeviceRunSessionEventsForDisplay([successful, failed])).toEqual([
successful,
failed,
]);
});
it('can withhold incomplete operations while following a running session', () => {
const started = createEventValue({
eventId: 'started',
type: 'operation.started',
operationId: 'operation-id',
summary: 'Started install',
});
const interaction = createEventValue({
eventId: 'interaction',
type: 'interaction.recorded',
operationId: 'another-operation-id',
summary: 'Captured screenshot screenshot.png',
});
const standalone = createEventValue({
eventId: 'standalone',
type: 'control.recorded',
summary: 'Stopping session',
});
expect(
projectDeviceRunSessionEventsForDisplay([started, interaction, standalone], {
includeIncompleteOperations: false,
})
).toEqual([standalone]);
expect(projectDeviceRunSessionEventsForDisplay([started, interaction, standalone])).toEqual([
started,
interaction,
standalone,
]);
});
it('keeps matching operation IDs from different producers separate', () => {
const agentStarted = createEventValue({
eventId: 'agent-started',
operationId: 'shared-operation-id',
summary: 'Started devices',
});
const agentCompleted = createEventValue({
eventId: 'agent-completed',
type: 'operation.completed',
operationId: 'shared-operation-id',
outcome: 'success',
summary: 'Found 2 devices',
});
const serveSimStarted = createEventValue({
eventId: 'serve-sim-started',
producer: 'serve-sim',
operationId: 'shared-operation-id',
summary: 'Started simulator activity',
});
expect(
projectDeviceRunSessionEventsForDisplay([agentStarted, agentCompleted, serveSimStarted]).map(
({ eventId }) => eventId
)
).toEqual(['agent-completed', 'serve-sim-started']);
});
});
describe(formatDeviceRunSessionEvent, () => {
it('formats a compact log line with the producer and duration', () => {
expect(
formatDeviceRunSessionEvent(
createEventValue({
ts: '2026-07-10T12:00:01.000Z',
summary: 'Found 2 devices',
durationMs: 123,
})
)
).toBe('2026-07-10T12:00:01.000Z [agent-device] Found 2 devices (123ms)');
});
it('includes a screenshot filename without exposing its full path', () => {
expect(
formatDeviceRunSessionEvent(
createEventValue({
summary: 'Ran screenshot',
data: {
command: 'screenshot',
path: '/tmp/private/screenshot.png',
},
})
)
).toBe('2026-07-10T12:00:00.000Z [agent-device] Ran screenshot screenshot.png');
});
it('strips ANSI escape sequences and control characters from terminal output', () => {
expect(
formatDeviceRunSessionEvent(
createEventValue({
ts: '\u001B[31m2026-07-10T12:00:00.000Z\u001B[0m\n',
producer: 'agent\u0000-device',
summary: 'Found\r2\tdevices\u0007',
})
)
).toBe('2026-07-10T12:00:00.000Z [agent-device] Found2devices');
});
});
describe(downloadDeviceRunSessionEventsAsync, () => {
beforeEach(() => {
jest.mocked(fetch).mockReset();
});
it('treats a missing event object as an empty log', async () => {
const response = new Response('', { status: 404 });
jest.mocked(fetch).mockRejectedValue(new RequestError('not found', response));
await expect(
downloadDeviceRunSessionEventsAsync('https://example.test/events')
).resolves.toEqual([]);
});
});
function createEventValue(overrides: Partial<DeviceRunSessionEvent> = {}): DeviceRunSessionEvent {
return {
v: 1,
eventId: 'event-id',
ts: '2026-07-10T12:00:00.000Z',
producer: 'agent-device',
type: 'operation.started',
summary: 'Started tap',
...overrides,
};
}
+120
View File
@@ -0,0 +1,120 @@
import { stripVTControlCharacters } from 'util';
import { type DeviceRunSessionEvent, parseDeviceRunSessionEvents } from '@expo/eas-build-job';
import fetch, { RequestError } from '../fetch';
import { formatMilliseconds } from '../utils/timer';
export type { DeviceRunSessionEvent };
export async function downloadDeviceRunSessionEventsAsync(
eventLogUrl: string
): Promise<DeviceRunSessionEvent[]> {
try {
const response = await fetch(eventLogUrl);
return parseDeviceRunSessionEvents(await response.text());
} catch (error) {
if (error instanceof RequestError && error.response.status === 404) {
return [];
}
throw error;
}
}
export function projectDeviceRunSessionEventsForDisplay(
events: DeviceRunSessionEvent[],
{ includeIncompleteOperations = true }: { includeIncompleteOperations?: boolean } = {}
): DeviceRunSessionEvent[] {
const operations = new Map<
string,
{
completed?: DeviceRunSessionEvent;
interactions: DeviceRunSessionEvent[];
}
>();
for (const event of events) {
if (!event.operationId) {
continue;
}
const operationKey = getOperationKey(event);
const operation = operations.get(operationKey) ?? { interactions: [] };
if (event.type === 'operation.completed') {
operation.completed = event;
} else if (event.type === 'interaction.recorded') {
operation.interactions.push(event);
}
operations.set(operationKey, operation);
}
return events.flatMap(event => {
if (!event.operationId) {
return [event];
}
const operation = operations.get(getOperationKey(event));
const completed = operation?.completed;
const interactions = operation?.interactions ?? [];
if (
!includeIncompleteOperations &&
!completed &&
(event.type === 'operation.started' || event.type === 'interaction.recorded')
) {
return [];
}
if (event.type === 'operation.started' && (completed || interactions.length > 0)) {
return [];
}
if (event.type === 'interaction.recorded' && completed?.outcome === 'failure') {
return [];
}
if (event.type === 'operation.completed' && completed?.outcome !== 'failure') {
return interactions.length > 0 ? [] : [event];
}
if (
event.type === 'interaction.recorded' &&
completed &&
interactions.at(-1)?.eventId === event.eventId
) {
return [{ ...event, durationMs: completed.durationMs, outcome: completed.outcome }];
}
return [event];
});
}
export function formatDeviceRunSessionEvent(event: DeviceRunSessionEvent): string {
const screenshotFilename = getScreenshotFilename(event);
const summary =
screenshotFilename && !event.summary.includes(screenshotFilename)
? `${event.summary} ${screenshotFilename}`
: event.summary;
const duration =
event.durationMs === undefined ? '' : ` (${formatEventDuration(event.durationMs)})`;
return stripTerminalControlCharacters(`${event.ts} [${event.producer}] ${summary}${duration}`);
}
function getOperationKey(event: DeviceRunSessionEvent): string {
return JSON.stringify([event.producer, event.operationId]);
}
function formatEventDuration(durationMs: number): string {
return durationMs < 1000 ? `${Math.round(durationMs)}ms` : formatMilliseconds(durationMs);
}
function getScreenshotFilename(event: DeviceRunSessionEvent): string | undefined {
if (event.data?.command !== 'screenshot' || typeof event.data.path !== 'string') {
return undefined;
}
return event.data.path.split(/[\\/]/).at(-1);
}
function stripTerminalControlCharacters(value: string): string {
return [...stripVTControlCharacters(value)]
.filter(character => {
const codePoint = character.codePointAt(0) ?? 0;
return codePoint >= 0x20 && !(codePoint >= 0x7f && codePoint <= 0x9f);
})
.join('');
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/eas-json",
"version": "21.3.0",
"version": "21.4.0",
"description": "A library for interacting with eas.json",
"homepage": "https://github.com/expo/eas-cli",
"bugs": "https://github.com/expo/eas-cli/issues",
@@ -32,7 +32,7 @@
},
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "21.3.0",
"@expo/eas-build-job": "21.4.0",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "eas-cli-local-build-plugin",
"version": "21.3.0",
"version": "21.4.0",
"description": "Tool for running EAS compatible builds on a local machine.",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
@@ -26,8 +26,8 @@
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
},
"dependencies": {
"@expo/build-tools": "21.3.0",
"@expo/eas-build-job": "21.3.0",
"@expo/build-tools": "21.4.0",
"@expo/eas-build-job": "21.4.0",
"@expo/logger": "21.0.0",
"@expo/spawn-async": "^1.7.2",
"@expo/turtle-spawn": "21.0.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/steps",
"version": "21.3.0",
"version": "21.4.0",
"bugs": "https://github.com/expo/eas-cli/issues",
"license": "BUSL-1.1",
"author": "Expo <[email protected]>",
@@ -27,7 +27,7 @@
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
},
"dependencies": {
"@expo/eas-build-job": "21.3.0",
"@expo/eas-build-job": "21.4.0",
"@expo/logger": "21.0.0",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@expo/worker",
"version": "21.3.0",
"version": "21.4.0",
"private": true,
"scripts": {
"start": "yarn watch",
@@ -19,16 +19,16 @@
},
"dependencies": {
"@babel/core": "7.24.7",
"@expo/build-tools": "21.3.0",
"@expo/build-tools": "21.4.0",
"@expo/config-plugins": "55.0.7",
"@expo/config-types": "55.0.5",
"@expo/downloader": "21.0.0",
"@expo/eas-build-job": "21.3.0",
"@expo/eas-build-job": "21.4.0",
"@expo/logger": "21.0.0",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "21.3.0",
"@expo/steps": "21.4.0",
"@expo/template-file": "21.0.2",
"@expo/turtle-spawn": "21.0.0",
"@hapi/boom": "10.0.1",
Generated
+17 -17
View File
@@ -2291,14 +2291,14 @@ __metadata:
languageName: node
linkType: hard
"@expo/build-tools@npm:21.3.0, @expo/build-tools@workspace:packages/build-tools":
"@expo/build-tools@npm:21.4.0, @expo/build-tools@workspace:packages/build-tools":
version: 0.0.0-use.local
resolution: "@expo/build-tools@workspace:packages/build-tools"
dependencies:
"@expo/config": "npm:55.0.10"
"@expo/config-plugins": "npm:55.0.7"
"@expo/downloader": "npm:21.0.0"
"@expo/eas-build-job": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.4.0"
"@expo/env": "npm:^0.4.0"
"@expo/logger": "npm:21.0.0"
"@expo/package-manager": "npm:1.9.10"
@@ -2306,7 +2306,7 @@ __metadata:
"@expo/repack-app": "npm:~0.6.1"
"@expo/results": "npm:^1.0.0"
"@expo/spawn-async": "npm:1.7.2"
"@expo/steps": "npm:21.3.0"
"@expo/steps": "npm:21.4.0"
"@expo/template-file": "npm:21.0.2"
"@expo/turtle-spawn": "npm:21.0.0"
"@expo/xcpretty": "npm:^4.3.1"
@@ -2547,7 +2547,7 @@ __metadata:
languageName: unknown
linkType: soft
"@expo/eas-build-job@npm:21.3.0, @expo/eas-build-job@workspace:packages/eas-build-job":
"@expo/eas-build-job@npm:21.4.0, @expo/eas-build-job@workspace:packages/eas-build-job":
version: 0.0.0-use.local
resolution: "@expo/eas-build-job@workspace:packages/eas-build-job"
dependencies:
@@ -2566,12 +2566,12 @@ __metadata:
languageName: unknown
linkType: soft
"@expo/eas-json@npm:21.3.0, @expo/eas-json@workspace:packages/eas-json":
"@expo/eas-json@npm:21.4.0, @expo/eas-json@workspace:packages/eas-json":
version: 0.0.0-use.local
resolution: "@expo/eas-json@workspace:packages/eas-json"
dependencies:
"@babel/code-frame": "npm:7.23.5"
"@expo/eas-build-job": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.4.0"
"@tsconfig/node20": "npm:20.1.8"
"@types/babel__code-frame": "npm:7.0.3"
"@types/fs-extra": "npm:11.0.4"
@@ -2884,11 +2884,11 @@ __metadata:
languageName: node
linkType: hard
"@expo/steps@npm:21.3.0, @expo/steps@workspace:packages/steps":
"@expo/steps@npm:21.4.0, @expo/steps@workspace:packages/steps":
version: 0.0.0-use.local
resolution: "@expo/steps@workspace:packages/steps"
dependencies:
"@expo/eas-build-job": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.4.0"
"@expo/logger": "npm:21.0.0"
"@expo/spawn-async": "npm:^1.7.2"
"@jest/globals": "npm:^29.7.0"
@@ -2957,16 +2957,16 @@ __metadata:
resolution: "@expo/worker@workspace:packages/worker"
dependencies:
"@babel/core": "npm:7.24.7"
"@expo/build-tools": "npm:21.3.0"
"@expo/build-tools": "npm:21.4.0"
"@expo/config-plugins": "npm:55.0.7"
"@expo/config-types": "npm:55.0.5"
"@expo/downloader": "npm:21.0.0"
"@expo/eas-build-job": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.4.0"
"@expo/logger": "npm:21.0.0"
"@expo/results": "npm:1.0.0"
"@expo/rudder-sdk-node": "npm:1.1.1"
"@expo/spawn-async": "npm:1.7.2"
"@expo/steps": "npm:21.3.0"
"@expo/steps": "npm:21.4.0"
"@expo/template-file": "npm:21.0.2"
"@expo/turtle-spawn": "npm:21.0.0"
"@hapi/boom": "npm:10.0.1"
@@ -9722,7 +9722,7 @@ __metadata:
resolution: "create-eas-build-function@workspace:packages/create-eas-build-function"
dependencies:
"@expo/package-manager": "npm:1.9.10"
"@expo/steps": "npm:21.3.0"
"@expo/steps": "npm:21.4.0"
"@jest/globals": "npm:^29.7.0"
"@types/jest": "npm:^29.5.12"
"@types/node": "npm:20.14.2"
@@ -10294,8 +10294,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "eas-cli-local-build-plugin@workspace:packages/local-build-plugin"
dependencies:
"@expo/build-tools": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.3.0"
"@expo/build-tools": "npm:21.4.0"
"@expo/eas-build-job": "npm:21.4.0"
"@expo/logger": "npm:21.0.0"
"@expo/spawn-async": "npm:^1.7.2"
"@expo/turtle-spawn": "npm:21.0.0"
@@ -10352,8 +10352,8 @@ __metadata:
"@expo/code-signing-certificates": "npm:0.0.5"
"@expo/config": "npm:55.0.10"
"@expo/config-plugins": "npm:55.0.7"
"@expo/eas-build-job": "npm:21.3.0"
"@expo/eas-json": "npm:21.3.0"
"@expo/eas-build-job": "npm:21.4.0"
"@expo/eas-json": "npm:21.4.0"
"@expo/env": "npm:^1.0.0"
"@expo/json-file": "npm:8.3.3"
"@expo/logger": "npm:21.0.0"
@@ -10368,7 +10368,7 @@ __metadata:
"@expo/results": "npm:1.0.0"
"@expo/rudder-sdk-node": "npm:1.1.1"
"@expo/spawn-async": "npm:1.7.2"
"@expo/steps": "npm:21.3.0"
"@expo/steps": "npm:21.4.0"
"@expo/timeago.js": "npm:1.0.0"
"@graphql-codegen/cli": "npm:6.3.1"
"@graphql-codegen/introspection": "npm:5.0.2"