mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08a9b5ff7f | ||
|
|
501af5213e | ||
|
|
d48f931d77 | ||
|
|
99b18a2189 | ||
|
|
9ece20157f |
No files matched your search
@@ -8,10 +8,16 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- [eas-cli] New command `observe:routes` for navigation metrics. ([#3730](https://github.com/expo/eas-cli/pull/3730) by [@douglowder](https://github.com/douglowder))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- [eas-cli] Refactor observe commands to extract common flag definitions. ([#3731](https://github.com/expo/eas-cli/pull/3731) by [@douglowder](https://github.com/douglowder))
|
||||
|
||||
## [18.12.3](https://github.com/expo/eas-cli/releases/tag/v18.12.3) - 2026-05-13
|
||||
|
||||
## [18.12.2](https://github.com/expo/eas-cli/releases/tag/v18.12.2) - 2026-05-12
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "18.12.3",
|
||||
"version": "18.13.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+100
-100
@@ -190,7 +190,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -207,7 +207,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:usage [ACCOUNT_NAME]`
|
||||
|
||||
@@ -229,7 +229,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -246,7 +246,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -260,7 +260,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -312,7 +312,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -333,7 +333,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -353,7 +353,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -373,7 +373,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -396,7 +396,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -434,7 +434,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -454,7 +454,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -472,7 +472,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -492,7 +492,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:dev`
|
||||
|
||||
@@ -517,7 +517,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:download`
|
||||
|
||||
@@ -544,7 +544,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -582,7 +582,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -622,7 +622,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -652,7 +652,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -680,7 +680,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -732,7 +732,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -750,7 +750,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -768,7 +768,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -785,7 +785,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -806,7 +806,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:delete [NAME]`
|
||||
|
||||
@@ -827,7 +827,7 @@ DESCRIPTION
|
||||
Delete a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -849,7 +849,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:insights`
|
||||
|
||||
@@ -873,7 +873,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/insights.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -893,7 +893,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -915,7 +915,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -937,7 +937,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -972,7 +972,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -995,7 +995,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -1016,7 +1016,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/v18.12.3/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -1033,7 +1033,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -1051,7 +1051,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [options]`
|
||||
|
||||
@@ -1080,7 +1080,7 @@ ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
@@ -1105,7 +1105,7 @@ ALIASES
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
|
||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||
|
||||
@@ -1126,7 +1126,7 @@ ALIASES
|
||||
$ eas worker:alias:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
|
||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||
|
||||
@@ -1147,7 +1147,7 @@ ALIASES
|
||||
$ eas worker:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
@@ -1184,7 +1184,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -1204,7 +1204,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -1225,7 +1225,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -1246,7 +1246,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -1260,7 +1260,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -1274,7 +1274,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1306,7 +1306,7 @@ DESCRIPTION
|
||||
create an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/env/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1332,7 +1332,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1354,7 +1354,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1382,7 +1382,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1410,7 +1410,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1433,7 +1433,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1455,7 +1455,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1489,7 +1489,7 @@ DESCRIPTION
|
||||
update an environment variable on the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/env/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||
|
||||
@@ -1533,7 +1533,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas fingerprint:generate`
|
||||
|
||||
@@ -1564,7 +1564,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1643,7 +1643,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
|
||||
|
||||
## `eas integrations:asc:disconnect`
|
||||
|
||||
@@ -1662,7 +1662,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
|
||||
|
||||
## `eas integrations:asc:status`
|
||||
|
||||
@@ -1680,7 +1680,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
|
||||
|
||||
## `eas integrations:convex:connect`
|
||||
|
||||
@@ -1702,7 +1702,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
|
||||
|
||||
## `eas integrations:convex:dashboard`
|
||||
|
||||
@@ -1716,7 +1716,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
|
||||
|
||||
## `eas integrations:convex:project`
|
||||
|
||||
@@ -1730,7 +1730,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
|
||||
|
||||
## `eas integrations:convex:project:delete`
|
||||
|
||||
@@ -1748,7 +1748,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team`
|
||||
|
||||
@@ -1762,7 +1762,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
|
||||
|
||||
## `eas integrations:convex:team:delete [CONVEX_TEAM]`
|
||||
|
||||
@@ -1783,7 +1783,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team:invite [CONVEX_TEAM]`
|
||||
|
||||
@@ -1803,7 +1803,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
|
||||
|
||||
## `eas login`
|
||||
|
||||
@@ -1855,7 +1855,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1873,7 +1873,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1891,7 +1891,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas new [PATH]`
|
||||
|
||||
@@ -1943,7 +1943,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1957,7 +1957,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1980,7 +1980,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:new [PATH]`
|
||||
|
||||
@@ -2004,7 +2004,7 @@ ALIASES
|
||||
$ eas new
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/project/new.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -2022,7 +2022,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -2055,7 +2055,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.12.3/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -2098,7 +2098,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -2119,7 +2119,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -2140,7 +2140,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -2165,7 +2165,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:insights GROUPID`
|
||||
|
||||
@@ -2192,7 +2192,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/insights.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -2219,7 +2219,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -2257,7 +2257,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:revert-update-rollout`
|
||||
|
||||
@@ -2285,7 +2285,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -2315,7 +2315,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -2336,7 +2336,7 @@ DESCRIPTION
|
||||
instead execute "eas update:republish" or "eas update:roll-back-to-embedded".
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -2360,7 +2360,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas upload`
|
||||
|
||||
@@ -2381,7 +2381,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/v18.12.3/packages/eas-cli/src/commands/upload.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.13.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -2403,7 +2403,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -2423,7 +2423,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -2442,7 +2442,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -2465,7 +2465,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -2482,7 +2482,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -2603,7 +2603,7 @@ DESCRIPTION
|
||||
to cancel.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
@@ -2623,7 +2623,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:logs [ID]`
|
||||
|
||||
@@ -2647,7 +2647,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
|
||||
## `eas workflow:run FILE`
|
||||
|
||||
@@ -2687,7 +2687,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:runs`
|
||||
|
||||
@@ -2709,7 +2709,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
|
||||
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
||||
|
||||
@@ -2737,7 +2737,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -2757,7 +2757,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
|
||||
## `eas workflow:view [ID]`
|
||||
|
||||
@@ -2779,5 +2779,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/v18.12.3/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/v18.13.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+12
@@ -28667,6 +28667,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "expoPackageVersion",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "fingerprintHash",
|
||||
"description": null,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"version": "18.12.3",
|
||||
"version": "18.13.0",
|
||||
"description": "EAS command line tool",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { getMockOclifConfig } from '../../../__tests__/commands/utils';
|
||||
import { AppPlatform } from '../../../graphql/generated';
|
||||
import { fetchObserveNavigationRoutesAsync } from '../../../observe/fetchNavigationRoutes';
|
||||
import {
|
||||
buildObserveNavigationRoutesJson,
|
||||
buildObserveNavigationRoutesTable,
|
||||
} from '../../../observe/formatNavigationRoutes';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||
import ObserveRoutes from '../routes';
|
||||
|
||||
jest.mock('../../../observe/fetchNavigationRoutes');
|
||||
jest.mock('../../../observe/formatNavigationRoutes', () => {
|
||||
const actual = jest.requireActual('../../../observe/formatNavigationRoutes');
|
||||
return {
|
||||
...actual,
|
||||
buildObserveNavigationRoutesTable: jest.fn().mockReturnValue('table'),
|
||||
buildObserveNavigationRoutesJson: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
});
|
||||
jest.mock('../../../log');
|
||||
jest.mock('../../../utils/json');
|
||||
|
||||
const mockFetchObserveNavigationRoutesAsync = jest.mocked(fetchObserveNavigationRoutesAsync);
|
||||
const mockBuildObserveNavigationRoutesTable = jest.mocked(buildObserveNavigationRoutesTable);
|
||||
const mockBuildObserveNavigationRoutesJson = jest.mocked(buildObserveNavigationRoutesJson);
|
||||
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
|
||||
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
|
||||
|
||||
describe(ObserveRoutes, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
const projectId = 'test-project-id';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockFetchObserveNavigationRoutesAsync.mockResolvedValue({
|
||||
routes: [],
|
||||
pageInfoByPlatform: new Map(),
|
||||
});
|
||||
});
|
||||
|
||||
function createCommand(argv: string[]): ObserveRoutes {
|
||||
const command = new ObserveRoutes(argv, mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
});
|
||||
return command;
|
||||
}
|
||||
|
||||
it('queries both platforms by default with all three navigation metric full names', async () => {
|
||||
const command = createCommand([]);
|
||||
await command.runAsync();
|
||||
|
||||
expect(mockFetchObserveNavigationRoutesAsync).toHaveBeenCalledTimes(1);
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.platforms).toEqual([AppPlatform.Android, AppPlatform.Ios]);
|
||||
expect(options.limit).toBe(50);
|
||||
|
||||
const tableCall = mockBuildObserveNavigationRoutesTable.mock.calls[0];
|
||||
expect(tableCall[1]).toEqual([
|
||||
'expo.navigation.cold_ttr',
|
||||
'expo.navigation.warm_ttr',
|
||||
'expo.navigation.tti',
|
||||
]);
|
||||
expect(tableCall[2]).toEqual(['median', 'count']);
|
||||
});
|
||||
|
||||
it('filters platform when --platform ios is provided', async () => {
|
||||
const command = createCommand(['--platform', 'ios']);
|
||||
await command.runAsync();
|
||||
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.platforms).toEqual([AppPlatform.Ios]);
|
||||
});
|
||||
|
||||
it('resolves --metric short aliases to navigation metric full names and deduplicates', async () => {
|
||||
const command = createCommand([
|
||||
'--metric',
|
||||
'cold_ttr',
|
||||
'--metric',
|
||||
'cold_ttr',
|
||||
'--metric',
|
||||
'nav_tti',
|
||||
]);
|
||||
await command.runAsync();
|
||||
|
||||
expect(mockBuildObserveNavigationRoutesTable.mock.calls[0][1]).toEqual([
|
||||
'expo.navigation.cold_ttr',
|
||||
'expo.navigation.tti',
|
||||
]);
|
||||
});
|
||||
|
||||
it('passes --limit and --after through to the fetcher', async () => {
|
||||
const command = createCommand(['--limit', '25', '--after', 'cursor-abc']);
|
||||
await command.runAsync();
|
||||
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.limit).toBe(25);
|
||||
expect(options.after).toBe('cursor-abc');
|
||||
});
|
||||
|
||||
it('uses --days to compute start/end time range', async () => {
|
||||
const now = new Date('2025-06-15T12:00:00.000Z');
|
||||
jest.useFakeTimers({ now });
|
||||
|
||||
const command = createCommand(['--days', '7']);
|
||||
await command.runAsync();
|
||||
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.endTime).toBe('2025-06-15T12:00:00.000Z');
|
||||
expect(options.startTime).toBe('2025-06-08T12:00:00.000Z');
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('uses explicit --start and --end when provided', async () => {
|
||||
const command = createCommand([
|
||||
'--start',
|
||||
'2025-01-01T00:00:00.000Z',
|
||||
'--end',
|
||||
'2025-02-01T00:00:00.000Z',
|
||||
]);
|
||||
await command.runAsync();
|
||||
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.startTime).toBe('2025-01-01T00:00:00.000Z');
|
||||
expect(options.endTime).toBe('2025-02-01T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('rejects --days combined with --start', async () => {
|
||||
const command = createCommand(['--days', '7', '--start', '2025-01-01T00:00:00.000Z']);
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('passes app-version, update-id, and build-number filters', async () => {
|
||||
const command = createCommand([
|
||||
'--app-version',
|
||||
'2.1.0',
|
||||
'--update-id',
|
||||
'update-xyz',
|
||||
'--build-number',
|
||||
'42',
|
||||
]);
|
||||
await command.runAsync();
|
||||
|
||||
const options = mockFetchObserveNavigationRoutesAsync.mock.calls[0][2];
|
||||
expect(options.appVersion).toBe('2.1.0');
|
||||
expect(options.updateId).toBe('update-xyz');
|
||||
expect(options.buildNumber).toBe('42');
|
||||
});
|
||||
|
||||
it('resolves --stat aliases and passes them through', async () => {
|
||||
const command = createCommand(['--stat', 'p90', '--stat', 'eventCount']);
|
||||
await command.runAsync();
|
||||
|
||||
expect(mockBuildObserveNavigationRoutesTable.mock.calls[0][2]).toEqual(['p90', 'count']);
|
||||
});
|
||||
|
||||
it('uses default JSON stats (median, p90, count) when --json is set without --stat', async () => {
|
||||
const command = createCommand(['--json', '--non-interactive']);
|
||||
await command.runAsync();
|
||||
|
||||
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||
expect(mockBuildObserveNavigationRoutesJson.mock.calls[0][2]).toEqual([
|
||||
'median',
|
||||
'p90',
|
||||
'count',
|
||||
]);
|
||||
expect(mockPrintJsonOnlyOutput).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -11,13 +11,17 @@ import {
|
||||
fetchTotalEventCountAsync,
|
||||
resolveOrderBy,
|
||||
} from '../../observe/fetchEvents';
|
||||
import {
|
||||
ObserveAfterFlag,
|
||||
ObserveAppVersionFlag,
|
||||
ObservePlatformFlag,
|
||||
ObserveProjectIdFlag,
|
||||
ObserveTimeRangeFlags,
|
||||
ObserveUpdateIdFlag,
|
||||
} from '../../observe/flags';
|
||||
import { METRIC_ALIASES, METRIC_SHORT_NAMES, resolveMetricName } from '../../observe/metricNames';
|
||||
import { buildObserveEventsJson, buildObserveEventsTable } from '../../observe/formatEvents';
|
||||
import {
|
||||
allowedPlatformFlagValues,
|
||||
appObservePlatformFromFlag,
|
||||
appPlatformsFromFlag,
|
||||
} from '../../observe/platforms';
|
||||
import { appObservePlatformFromFlag, appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { resolveObserveCommandContextAsync } from '../../observe/resolveProjectContext';
|
||||
import { resolveTimeRange } from '../../observe/startAndEndTime';
|
||||
import { selectAsync } from '../../prompts';
|
||||
@@ -44,40 +48,16 @@ export default class ObserveEvents extends EasCommand {
|
||||
required: false,
|
||||
default: EventsOrderPreset.Oldest.valueOf().toLowerCase(),
|
||||
})(),
|
||||
platform: Flags.option({
|
||||
description: 'Filter by platform',
|
||||
options: allowedPlatformFlagValues,
|
||||
})(),
|
||||
after: Flags.string({
|
||||
description:
|
||||
'Cursor for pagination. Use the endCursor from a previous query to fetch the next page.',
|
||||
}),
|
||||
...ObservePlatformFlag,
|
||||
...ObserveAfterFlag,
|
||||
limit: getLimitFlagWithCustomValues({
|
||||
defaultTo: DEFAULT_EVENTS_LIMIT,
|
||||
limit: 100,
|
||||
}),
|
||||
start: Flags.string({
|
||||
description: 'Start of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
end: Flags.string({
|
||||
description: 'End of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
days: Flags.integer({
|
||||
description: 'Show events from the last N days (mutually exclusive with --start/--end)',
|
||||
min: 1,
|
||||
exclusive: ['start', 'end'],
|
||||
}),
|
||||
'app-version': Flags.string({
|
||||
description: 'Filter by app version',
|
||||
}),
|
||||
'update-id': Flags.string({
|
||||
description: 'Filter by EAS update ID',
|
||||
}),
|
||||
'project-id': Flags.string({
|
||||
description: 'EAS project ID (defaults to the project ID of the current directory)',
|
||||
}),
|
||||
...ObserveTimeRangeFlags,
|
||||
...ObserveAppVersionFlag,
|
||||
...ObserveUpdateIdFlag,
|
||||
...ObserveProjectIdFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@ import { getLimitFlagWithCustomValues } from '../../commandUtils/pagination';
|
||||
import Log from '../../log';
|
||||
import { ObserveQuery } from '../../graphql/queries/ObserveQuery';
|
||||
import { fetchObserveCustomEventsAsync } from '../../observe/fetchCustomEvents';
|
||||
import {
|
||||
ObserveAfterFlag,
|
||||
ObserveAppVersionFlag,
|
||||
ObservePlatformFlag,
|
||||
ObserveProjectIdFlag,
|
||||
ObserveTimeRangeFlags,
|
||||
ObserveUpdateIdFlag,
|
||||
} from '../../observe/flags';
|
||||
import {
|
||||
buildObserveCustomEventNamesJson,
|
||||
buildObserveCustomEventNamesTable,
|
||||
@@ -14,7 +22,7 @@ import {
|
||||
buildObserveCustomEventsJson,
|
||||
buildObserveCustomEventsTable,
|
||||
} from '../../observe/formatCustomEvents';
|
||||
import { allowedPlatformFlagValues, appObservePlatformFromFlag } from '../../observe/platforms';
|
||||
import { appObservePlatformFromFlag } from '../../observe/platforms';
|
||||
import { resolveObserveCommandContextAsync } from '../../observe/resolveProjectContext';
|
||||
import { resolveTimeRange } from '../../observe/startAndEndTime';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
@@ -34,37 +42,15 @@ export default class ObserveLogs extends EasCommand {
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.option({
|
||||
description: 'Filter by platform',
|
||||
options: allowedPlatformFlagValues,
|
||||
})(),
|
||||
after: Flags.string({
|
||||
description:
|
||||
'Cursor for pagination. Use the endCursor from a previous query to fetch the next page.',
|
||||
}),
|
||||
...ObservePlatformFlag,
|
||||
...ObserveAfterFlag,
|
||||
limit: getLimitFlagWithCustomValues({
|
||||
defaultTo: DEFAULT_EVENTS_LIMIT,
|
||||
limit: 100,
|
||||
}),
|
||||
start: Flags.string({
|
||||
description: 'Start of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
end: Flags.string({
|
||||
description: 'End of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
days: Flags.integer({
|
||||
description: 'Show events from the last N days (mutually exclusive with --start/--end)',
|
||||
min: 1,
|
||||
exclusive: ['start', 'end'],
|
||||
}),
|
||||
'app-version': Flags.string({
|
||||
description: 'Filter by app version',
|
||||
}),
|
||||
'update-id': Flags.string({
|
||||
description: 'Filter by EAS update ID',
|
||||
}),
|
||||
...ObserveTimeRangeFlags,
|
||||
...ObserveAppVersionFlag,
|
||||
...ObserveUpdateIdFlag,
|
||||
'session-id': Flags.string({
|
||||
description: 'Filter by session ID',
|
||||
}),
|
||||
@@ -73,9 +59,7 @@ export default class ObserveLogs extends EasCommand {
|
||||
'When no event name argument is provided, list all events across all event names instead of a summary of event names + counts.',
|
||||
default: false,
|
||||
}),
|
||||
'project-id': Flags.string({
|
||||
description: 'EAS project ID (defaults to the project ID of the current directory)',
|
||||
}),
|
||||
...ObserveProjectIdFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import Log from '../../log';
|
||||
import { fetchObserveMetricsAsync } from '../../observe/fetchMetrics';
|
||||
import {
|
||||
ObservePlatformFlag,
|
||||
ObserveProjectIdFlag,
|
||||
ObserveTimeRangeFlags,
|
||||
} from '../../observe/flags';
|
||||
import {
|
||||
StatisticKey,
|
||||
buildObserveMetricsJson,
|
||||
@@ -11,7 +16,7 @@ import {
|
||||
resolveStatKey,
|
||||
} from '../../observe/formatMetrics';
|
||||
import { METRIC_ALIASES, resolveMetricName } from '../../observe/metricNames';
|
||||
import { allowedPlatformFlagValues, appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { resolveObserveCommandContextAsync } from '../../observe/resolveProjectContext';
|
||||
import { resolveTimeRange } from '../../observe/startAndEndTime';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
@@ -41,10 +46,7 @@ export default class ObserveMetrics extends EasCommand {
|
||||
static override description = 'display app performance metrics grouped by app version';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.option({
|
||||
description: 'Filter by platform',
|
||||
options: allowedPlatformFlagValues,
|
||||
})(),
|
||||
...ObservePlatformFlag,
|
||||
metric: Flags.option({
|
||||
description: 'Metric name to display (can be specified multiple times).',
|
||||
multiple: true,
|
||||
@@ -55,22 +57,8 @@ export default class ObserveMetrics extends EasCommand {
|
||||
multiple: true,
|
||||
options: DEFAULT_STATS_JSON,
|
||||
})(),
|
||||
start: Flags.string({
|
||||
description: 'Start of time range for metrics data (ISO date).',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
end: Flags.string({
|
||||
description: 'End of time range for metrics data (ISO date).',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
days: Flags.integer({
|
||||
description: 'Show metrics from the last N days (mutually exclusive with --start/--end)',
|
||||
min: 1,
|
||||
exclusive: ['start', 'end'],
|
||||
}),
|
||||
'project-id': Flags.string({
|
||||
description: 'EAS project ID (defaults to the project ID of the current directory)',
|
||||
}),
|
||||
...ObserveTimeRangeFlags,
|
||||
...ObserveProjectIdFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { getLimitFlagWithCustomValues } from '../../commandUtils/pagination';
|
||||
import Log from '../../log';
|
||||
import { fetchObserveNavigationRoutesAsync } from '../../observe/fetchNavigationRoutes';
|
||||
import {
|
||||
ObserveAfterFlag,
|
||||
ObserveAppVersionFlag,
|
||||
ObservePlatformFlag,
|
||||
ObserveProjectIdFlag,
|
||||
ObserveTimeRangeFlags,
|
||||
ObserveUpdateIdFlag,
|
||||
} from '../../observe/flags';
|
||||
import {
|
||||
NAVIGATION_METRIC_NAMES,
|
||||
NavigationStatKey,
|
||||
buildObserveNavigationRoutesJson,
|
||||
buildObserveNavigationRoutesTable,
|
||||
resolveNavigationStatKey,
|
||||
} from '../../observe/formatNavigationRoutes';
|
||||
import { NAVIGATION_METRIC_ALIASES, resolveNavigationMetricName } from '../../observe/metricNames';
|
||||
import { appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { resolveObserveCommandContextAsync } from '../../observe/resolveProjectContext';
|
||||
import { resolveTimeRange } from '../../observe/startAndEndTime';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
const DEFAULT_ROUTES_LIMIT = 50;
|
||||
|
||||
const STAT_OPTIONS = ['median', 'med', 'p90', 'count', 'event_count', 'eventCount'];
|
||||
|
||||
const DEFAULT_STATS_TABLE: NavigationStatKey[] = ['median', 'count'];
|
||||
const DEFAULT_STATS_JSON: NavigationStatKey[] = ['median', 'p90', 'count'];
|
||||
|
||||
export default class ObserveRoutes extends EasCommand {
|
||||
static override hidden = true;
|
||||
static override description =
|
||||
'display app navigation route metrics (Cold TTR, Warm TTR, TTI) grouped by route name';
|
||||
|
||||
static override flags = {
|
||||
...ObservePlatformFlag,
|
||||
metric: Flags.option({
|
||||
description:
|
||||
'Navigation metric to display (can be specified multiple times). Defaults to all three.',
|
||||
multiple: true,
|
||||
options: Object.keys(NAVIGATION_METRIC_ALIASES),
|
||||
})(),
|
||||
stat: Flags.option({
|
||||
description: 'Statistic to display per metric (can be specified multiple times)',
|
||||
multiple: true,
|
||||
options: STAT_OPTIONS,
|
||||
})(),
|
||||
...ObserveAfterFlag,
|
||||
limit: getLimitFlagWithCustomValues({
|
||||
defaultTo: DEFAULT_ROUTES_LIMIT,
|
||||
limit: 200,
|
||||
}),
|
||||
...ObserveTimeRangeFlags,
|
||||
...ObserveAppVersionFlag,
|
||||
...ObserveUpdateIdFlag,
|
||||
'build-number': Flags.string({
|
||||
description: 'Filter by app build number',
|
||||
}),
|
||||
...ObserveProjectIdFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
private static loggedInOnlyContextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(ObserveRoutes);
|
||||
|
||||
const { projectId, graphqlClient } = await resolveObserveCommandContextAsync({
|
||||
command: this,
|
||||
commandClass: ObserveRoutes,
|
||||
loggedInOnlyContextDefinition: ObserveRoutes.loggedInOnlyContextDefinition,
|
||||
projectIdOverride: flags['project-id'],
|
||||
nonInteractive: flags['non-interactive'],
|
||||
});
|
||||
|
||||
if (flags.json) {
|
||||
enableJsonOutput();
|
||||
} else {
|
||||
Log.warn('EAS Observe is in preview and subject to breaking changes.');
|
||||
}
|
||||
|
||||
const metricNames = flags.metric?.length
|
||||
? Array.from(new Set(flags.metric.map(resolveNavigationMetricName)))
|
||||
: NAVIGATION_METRIC_NAMES;
|
||||
|
||||
const argumentsStat = flags.stat?.length
|
||||
? Array.from(new Set(flags.stat.map(resolveNavigationStatKey)))
|
||||
: undefined;
|
||||
|
||||
const { daysBack, startTime, endTime } = resolveTimeRange(flags);
|
||||
const platforms = appPlatformsFromFlag(flags.platform);
|
||||
|
||||
const { routes, pageInfoByPlatform } = await fetchObserveNavigationRoutesAsync(
|
||||
graphqlClient,
|
||||
projectId,
|
||||
{
|
||||
startTime,
|
||||
endTime,
|
||||
platforms,
|
||||
limit: flags.limit ?? DEFAULT_ROUTES_LIMIT,
|
||||
...(flags.after && { after: flags.after }),
|
||||
appVersion: flags['app-version'],
|
||||
updateId: flags['update-id'],
|
||||
buildNumber: flags['build-number'],
|
||||
}
|
||||
);
|
||||
|
||||
if (flags.json) {
|
||||
const stats = argumentsStat ?? DEFAULT_STATS_JSON;
|
||||
printJsonOnlyOutput(
|
||||
buildObserveNavigationRoutesJson(routes, metricNames, stats, pageInfoByPlatform)
|
||||
);
|
||||
} else {
|
||||
const stats = argumentsStat ?? DEFAULT_STATS_TABLE;
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
buildObserveNavigationRoutesTable(routes, metricNames, stats, {
|
||||
daysBack,
|
||||
startTime,
|
||||
endTime,
|
||||
pageInfoByPlatform,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import Log from '../../log';
|
||||
import { fetchObserveVersionsAsync } from '../../observe/fetchVersions';
|
||||
import {
|
||||
ObservePlatformFlag,
|
||||
ObserveProjectIdFlag,
|
||||
ObserveTimeRangeFlags,
|
||||
} from '../../observe/flags';
|
||||
import { buildObserveVersionsJson, buildObserveVersionsTable } from '../../observe/formatVersions';
|
||||
import { allowedPlatformFlagValues, appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { appPlatformsFromFlag } from '../../observe/platforms';
|
||||
import { resolveObserveCommandContextAsync } from '../../observe/resolveProjectContext';
|
||||
import { resolveTimeRange } from '../../observe/startAndEndTime';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
@@ -15,26 +18,9 @@ export default class ObserveVersions extends EasCommand {
|
||||
static override description = 'display app versions with build and update details';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.option({
|
||||
description: 'Filter by platform',
|
||||
options: allowedPlatformFlagValues,
|
||||
})(),
|
||||
start: Flags.string({
|
||||
description: 'Start of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
end: Flags.string({
|
||||
description: 'End of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
days: Flags.integer({
|
||||
description: 'Show versions from the last N days (mutually exclusive with --start/--end)',
|
||||
min: 1,
|
||||
exclusive: ['start', 'end'],
|
||||
}),
|
||||
'project-id': Flags.string({
|
||||
description: 'EAS project ID (defaults to the project ID of the current directory)',
|
||||
}),
|
||||
...ObservePlatformFlag,
|
||||
...ObserveTimeRangeFlags,
|
||||
...ObserveProjectIdFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
|
||||
+12
@@ -4029,6 +4029,7 @@ export type BuildMetadataInput = {
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
environment?: InputMaybe<Scalars['String']['input']>;
|
||||
expoPackageVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
fingerprintHash?: InputMaybe<Scalars['String']['input']>;
|
||||
fingerprintSource?: InputMaybe<FingerprintSourceInput>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -12903,6 +12904,17 @@ export type AppObserveCustomEventNamesQueryVariables = Exact<{
|
||||
|
||||
export type AppObserveCustomEventNamesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, observe: { __typename?: 'AppObserve', customEventNames: { __typename?: 'AppObserveCustomEventNames', isTruncated: boolean, names: Array<{ __typename?: 'AppObserveCustomEventName', eventName: string, count: number }> } } } } };
|
||||
|
||||
export type AppObserveNavigationRoutesQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
filter: AppObserveNavigationRoutesFilter;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
orderBy?: InputMaybe<AppObserveNavigationRoutesOrderBy>;
|
||||
}>;
|
||||
|
||||
|
||||
export type AppObserveNavigationRoutesQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, observe: { __typename?: 'AppObserve', navigationRoutes: { __typename?: 'AppObserveNavigationRoutesConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null }, edges: Array<{ __typename?: 'AppObserveNavigationRouteEdge', cursor: string, node: { __typename?: 'AppObserveNavigationRoute', routeName: string, coldTtr: { __typename?: 'AppObserveNavigationStat', count: number, median?: number | null, p90?: number | null }, warmTtr: { __typename?: 'AppObserveNavigationStat', count: number, median?: number | null, p90?: number | null }, tti: { __typename?: 'AppObserveNavigationStat', count: number, median?: number | null, p90?: number | null } } }> } } } } };
|
||||
|
||||
export type GetAssetMetadataQueryVariables = Exact<{
|
||||
storageKeys: Array<Scalars['String']['input']> | Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
AppObserveEvent,
|
||||
AppObserveEventsFilter,
|
||||
AppObserveEventsOrderBy,
|
||||
AppObserveNavigationRoute,
|
||||
AppObserveNavigationRoutesFilter,
|
||||
AppObserveNavigationRoutesOrderBy,
|
||||
AppObservePlatform,
|
||||
AppObserveReleasesInput,
|
||||
AppObserveTimeSeriesInput,
|
||||
@@ -133,6 +136,31 @@ type AppObserveCustomEventNamesQueryVariables = {
|
||||
environment?: string;
|
||||
};
|
||||
|
||||
type AppObserveNavigationRoutesQuery = {
|
||||
app: {
|
||||
byId: {
|
||||
id: string;
|
||||
observe: {
|
||||
navigationRoutes: {
|
||||
pageInfo: PageInfo;
|
||||
edges: Array<{
|
||||
cursor: string;
|
||||
node: AppObserveNavigationRoute;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type AppObserveNavigationRoutesQueryVariables = {
|
||||
appId: string;
|
||||
filter: AppObserveNavigationRoutesFilter;
|
||||
first?: number;
|
||||
after?: string;
|
||||
orderBy?: AppObserveNavigationRoutesOrderBy;
|
||||
};
|
||||
|
||||
export const ObserveQuery = {
|
||||
async timeSeriesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
@@ -398,4 +426,68 @@ export const ObserveQuery = {
|
||||
|
||||
return data.app.byId.observe.customEventNames;
|
||||
},
|
||||
|
||||
async navigationRoutesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
variables: AppObserveNavigationRoutesQueryVariables
|
||||
): Promise<{ routes: AppObserveNavigationRoute[]; pageInfo: PageInfo }> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppObserveNavigationRoutesQuery, AppObserveNavigationRoutesQueryVariables>(
|
||||
gql`
|
||||
query AppObserveNavigationRoutes(
|
||||
$appId: String!
|
||||
$filter: AppObserveNavigationRoutesFilter!
|
||||
$first: Int
|
||||
$after: String
|
||||
$orderBy: AppObserveNavigationRoutesOrderBy
|
||||
) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
observe {
|
||||
navigationRoutes(filter: $filter, first: $first, after: $after, orderBy: $orderBy) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
routeName
|
||||
coldTtr {
|
||||
count
|
||||
median
|
||||
p90
|
||||
}
|
||||
warmTtr {
|
||||
count
|
||||
median
|
||||
p90
|
||||
}
|
||||
tti {
|
||||
count
|
||||
median
|
||||
p90
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
const { edges, pageInfo } = data.app.byId.observe.navigationRoutes;
|
||||
return {
|
||||
routes: edges.map(edge => edge.node),
|
||||
pageInfo,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
AppObserveEventsOrderByDirection,
|
||||
AppObserveNavigationRoute,
|
||||
AppObserveNavigationRoutesOrderByField,
|
||||
AppObservePlatform,
|
||||
AppPlatform,
|
||||
} from '../../graphql/generated';
|
||||
import { ObserveQuery } from '../../graphql/queries/ObserveQuery';
|
||||
import { fetchObserveNavigationRoutesAsync } from '../fetchNavigationRoutes';
|
||||
|
||||
jest.mock('../../graphql/queries/ObserveQuery');
|
||||
jest.mock('../../log');
|
||||
|
||||
function makeRoute(routeName: string): AppObserveNavigationRoute {
|
||||
return {
|
||||
__typename: 'AppObserveNavigationRoute' as const,
|
||||
routeName,
|
||||
coldTtr: { __typename: 'AppObserveNavigationStat' as const, count: 10, median: 0.5, p90: 0.9 },
|
||||
warmTtr: { __typename: 'AppObserveNavigationStat' as const, count: 8, median: 0.3, p90: 0.6 },
|
||||
tti: { __typename: 'AppObserveNavigationStat' as const, count: 12, median: 0.45, p90: 0.7 },
|
||||
};
|
||||
}
|
||||
|
||||
describe('fetchObserveNavigationRoutesAsync', () => {
|
||||
const mockNavigationRoutesAsync = jest.mocked(ObserveQuery.navigationRoutesAsync);
|
||||
const mockGraphqlClient = {} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockNavigationRoutesAsync.mockClear();
|
||||
mockNavigationRoutesAsync.mockResolvedValue({
|
||||
routes: [],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false },
|
||||
});
|
||||
});
|
||||
|
||||
it('passes appId, time range, limit, and filter values for each platform', async () => {
|
||||
await fetchObserveNavigationRoutesAsync(mockGraphqlClient, 'project-123', {
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios],
|
||||
limit: 25,
|
||||
appVersion: '2.1.0',
|
||||
updateId: 'update-xyz',
|
||||
buildNumber: '42',
|
||||
});
|
||||
|
||||
expect(mockNavigationRoutesAsync).toHaveBeenCalledTimes(1);
|
||||
const call = mockNavigationRoutesAsync.mock.calls[0][1];
|
||||
expect(call.appId).toBe('project-123');
|
||||
expect(call.first).toBe(25);
|
||||
expect(call.filter).toEqual({
|
||||
platform: AppObservePlatform.Ios,
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
appVersion: '2.1.0',
|
||||
appUpdateId: 'update-xyz',
|
||||
appBuildNumber: '42',
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults orderBy to NAVIGATION_COUNT DESC', async () => {
|
||||
await fetchObserveNavigationRoutesAsync(mockGraphqlClient, 'project-123', {
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios],
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
const call = mockNavigationRoutesAsync.mock.calls[0][1];
|
||||
expect(call.orderBy).toEqual({
|
||||
field: AppObserveNavigationRoutesOrderByField.NavigationCount,
|
||||
direction: AppObserveEventsOrderByDirection.Desc,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send optional filters when not provided', async () => {
|
||||
await fetchObserveNavigationRoutesAsync(mockGraphqlClient, 'project-123', {
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios],
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
const filter = mockNavigationRoutesAsync.mock.calls[0][1].filter;
|
||||
expect(filter).not.toHaveProperty('appVersion');
|
||||
expect(filter).not.toHaveProperty('appUpdateId');
|
||||
expect(filter).not.toHaveProperty('appBuildNumber');
|
||||
});
|
||||
|
||||
it('forwards after cursor when provided', async () => {
|
||||
await fetchObserveNavigationRoutesAsync(mockGraphqlClient, 'project-123', {
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios],
|
||||
limit: 50,
|
||||
after: 'cursor-abc',
|
||||
});
|
||||
|
||||
expect(mockNavigationRoutesAsync.mock.calls[0][1].after).toBe('cursor-abc');
|
||||
});
|
||||
|
||||
it('fans out across multiple platforms and tags each route with its platform', async () => {
|
||||
mockNavigationRoutesAsync.mockImplementation(async (_client, vars) => {
|
||||
if (vars.filter.platform === AppObservePlatform.Android) {
|
||||
return {
|
||||
routes: [makeRoute('/android-home')],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false },
|
||||
};
|
||||
}
|
||||
return {
|
||||
routes: [makeRoute('/ios-home')],
|
||||
pageInfo: { hasNextPage: true, hasPreviousPage: false, endCursor: 'ios-cursor' },
|
||||
};
|
||||
});
|
||||
|
||||
const { routes, pageInfoByPlatform } = await fetchObserveNavigationRoutesAsync(
|
||||
mockGraphqlClient,
|
||||
'project-123',
|
||||
{
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios, AppPlatform.Android],
|
||||
limit: 50,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockNavigationRoutesAsync).toHaveBeenCalledTimes(2);
|
||||
expect(routes).toHaveLength(2);
|
||||
const byName = new Map(routes.map(r => [r.route.routeName, r.platform]));
|
||||
expect(byName.get('/android-home')).toBe(AppPlatform.Android);
|
||||
expect(byName.get('/ios-home')).toBe(AppPlatform.Ios);
|
||||
expect(pageInfoByPlatform.get(AppPlatform.Ios)?.hasNextPage).toBe(true);
|
||||
expect(pageInfoByPlatform.get(AppPlatform.Android)?.hasNextPage).toBe(false);
|
||||
});
|
||||
|
||||
it('handles partial failures gracefully', async () => {
|
||||
mockNavigationRoutesAsync.mockImplementation(async (_client, vars) => {
|
||||
if (vars.filter.platform === AppObservePlatform.Android) {
|
||||
throw new Error('Network error');
|
||||
}
|
||||
return {
|
||||
routes: [makeRoute('/home')],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false },
|
||||
};
|
||||
});
|
||||
|
||||
const { routes, pageInfoByPlatform } = await fetchObserveNavigationRoutesAsync(
|
||||
mockGraphqlClient,
|
||||
'project-123',
|
||||
{
|
||||
startTime: '2025-01-01T00:00:00.000Z',
|
||||
endTime: '2025-03-01T00:00:00.000Z',
|
||||
platforms: [AppPlatform.Ios, AppPlatform.Android],
|
||||
limit: 50,
|
||||
}
|
||||
);
|
||||
|
||||
expect(routes).toHaveLength(1);
|
||||
expect(routes[0].platform).toBe(AppPlatform.Ios);
|
||||
expect(pageInfoByPlatform.has(AppPlatform.Android)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -71,12 +71,12 @@ describe(buildObserveMetricsTable, () => {
|
||||
"[1mMed values (event count)[22m
|
||||
|
||||
[1miOS[22m
|
||||
[1mApp Version Cold Launch TTI [22m
|
||||
----------- ----------- ----------
|
||||
1.2.0 0.35s (110) 1.32s (90)
|
||||
[1mApp Version Cold Launch Startup TTI[22m
|
||||
----------- ----------- -----------
|
||||
1.2.0 0.35s (110) 1.32s (90)
|
||||
|
||||
[1mAndroid[22m
|
||||
[1mApp Version Cold Launch TTI [22m
|
||||
[1mApp Version Cold Launch Startup TTI[22m
|
||||
----------- ----------- -----------
|
||||
1.1.0 0.25s (120) 1.12s (100)"
|
||||
`);
|
||||
@@ -93,9 +93,9 @@ describe(buildObserveMetricsTable, () => {
|
||||
"[1mMed values (event count)[22m
|
||||
|
||||
[1miOS[22m
|
||||
[1mApp Version Cold Launch TTI [22m
|
||||
----------- ----------- -----
|
||||
2.0.0 - (-) - (-)"
|
||||
[1mApp Version Cold Launch Startup TTI[22m
|
||||
----------- ----------- -----------
|
||||
2.0.0 - (-) - (-) "
|
||||
`);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import { AppPlatform } from '../../graphql/generated';
|
||||
import { NavigationRouteWithPlatform } from '../fetchNavigationRoutes';
|
||||
import {
|
||||
buildObserveNavigationRoutesJson,
|
||||
buildObserveNavigationRoutesTable,
|
||||
isNavigationMetric,
|
||||
resolveNavigationStatKey,
|
||||
} from '../formatNavigationRoutes';
|
||||
|
||||
function makeRoute(
|
||||
routeName: string,
|
||||
platform: AppPlatform,
|
||||
overrides?: Partial<{
|
||||
coldTtr: { count: number; median: number | null; p90: number | null };
|
||||
warmTtr: { count: number; median: number | null; p90: number | null };
|
||||
tti: { count: number; median: number | null; p90: number | null };
|
||||
}>
|
||||
): NavigationRouteWithPlatform {
|
||||
return {
|
||||
platform,
|
||||
route: {
|
||||
__typename: 'AppObserveNavigationRoute' as const,
|
||||
routeName,
|
||||
coldTtr: {
|
||||
__typename: 'AppObserveNavigationStat' as const,
|
||||
count: 10,
|
||||
median: 0.5,
|
||||
p90: 0.9,
|
||||
...overrides?.coldTtr,
|
||||
},
|
||||
warmTtr: {
|
||||
__typename: 'AppObserveNavigationStat' as const,
|
||||
count: 8,
|
||||
median: 0.3,
|
||||
p90: 0.6,
|
||||
...overrides?.warmTtr,
|
||||
},
|
||||
tti: {
|
||||
__typename: 'AppObserveNavigationStat' as const,
|
||||
count: 12,
|
||||
median: 0.45,
|
||||
p90: 0.7,
|
||||
...overrides?.tti,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe(isNavigationMetric, () => {
|
||||
it('returns true for each known navigation metric full name', () => {
|
||||
expect(isNavigationMetric('expo.navigation.cold_ttr')).toBe(true);
|
||||
expect(isNavigationMetric('expo.navigation.warm_ttr')).toBe(true);
|
||||
expect(isNavigationMetric('expo.navigation.tti')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for app-startup metrics or unknown names', () => {
|
||||
expect(isNavigationMetric('expo.app_startup.tti')).toBe(false);
|
||||
expect(isNavigationMetric('cold_ttr')).toBe(false);
|
||||
expect(isNavigationMetric('unknown')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveNavigationStatKey, () => {
|
||||
it('resolves aliases', () => {
|
||||
expect(resolveNavigationStatKey('med')).toBe('median');
|
||||
expect(resolveNavigationStatKey('median')).toBe('median');
|
||||
expect(resolveNavigationStatKey('p90')).toBe('p90');
|
||||
expect(resolveNavigationStatKey('count')).toBe('count');
|
||||
expect(resolveNavigationStatKey('eventCount')).toBe('count');
|
||||
expect(resolveNavigationStatKey('event_count')).toBe('count');
|
||||
});
|
||||
|
||||
it('throws on unknown stat', () => {
|
||||
expect(() => resolveNavigationStatKey('min')).toThrow('Unknown statistic');
|
||||
});
|
||||
});
|
||||
|
||||
describe(buildObserveNavigationRoutesTable, () => {
|
||||
it('returns a yellow warning when no routes are present', () => {
|
||||
const output = buildObserveNavigationRoutesTable(
|
||||
[],
|
||||
['expo.navigation.cold_ttr'],
|
||||
['median', 'count']
|
||||
);
|
||||
expect(output).toContain('No navigation routes found.');
|
||||
});
|
||||
|
||||
it('formats routes grouped by platform with merged (med + count) cells', () => {
|
||||
const routes = [
|
||||
makeRoute('/home', AppPlatform.Ios),
|
||||
makeRoute('/profile', AppPlatform.Ios, {
|
||||
coldTtr: { count: 5, median: 0.7, p90: 1.0 },
|
||||
}),
|
||||
makeRoute('/home', AppPlatform.Android),
|
||||
];
|
||||
|
||||
const output = buildObserveNavigationRoutesTable(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr', 'expo.navigation.warm_ttr', 'expo.navigation.tti'],
|
||||
['median', 'count'],
|
||||
{ daysBack: 7 }
|
||||
);
|
||||
|
||||
expect(output).toContain('Med values (navigation count) for the last 7 days');
|
||||
expect(output).toContain('Cold TTR');
|
||||
expect(output).toContain('Warm TTR');
|
||||
expect(output).toContain('Nav TTI');
|
||||
expect(output).toContain('/home');
|
||||
expect(output).toContain('/profile');
|
||||
expect(output).toContain('0.50s (10)');
|
||||
expect(output).toContain('0.70s (5)');
|
||||
expect(output).toContain('iOS');
|
||||
expect(output).toContain('Android');
|
||||
});
|
||||
|
||||
it('renders separate columns when count is omitted from stats', () => {
|
||||
const routes = [makeRoute('/home', AppPlatform.Ios)];
|
||||
const output = buildObserveNavigationRoutesTable(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr'],
|
||||
['median', 'p90']
|
||||
);
|
||||
|
||||
expect(output).toContain('Med, P90 values');
|
||||
expect(output).toContain('Cold TTR Med');
|
||||
expect(output).toContain('Cold TTR P90');
|
||||
expect(output).toContain('0.50s');
|
||||
expect(output).toContain('0.90s');
|
||||
});
|
||||
|
||||
it('renders count-only column when only count stat is requested', () => {
|
||||
const routes = [makeRoute('/home', AppPlatform.Ios)];
|
||||
const output = buildObserveNavigationRoutesTable(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr'],
|
||||
['count']
|
||||
);
|
||||
|
||||
expect(output).toContain('Navigation count values');
|
||||
expect(output).toContain('Cold TTR Count');
|
||||
expect(output).toContain('10');
|
||||
});
|
||||
|
||||
it('shows a next-page hint per platform when hasNextPage is true', () => {
|
||||
const routes = [makeRoute('/home', AppPlatform.Ios)];
|
||||
const pageInfoByPlatform = new Map([
|
||||
[AppPlatform.Ios, { hasNextPage: true, endCursor: 'cursor-ios' }],
|
||||
]);
|
||||
|
||||
const output = buildObserveNavigationRoutesTable(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr'],
|
||||
['median', 'count'],
|
||||
{ pageInfoByPlatform }
|
||||
);
|
||||
|
||||
expect(output).toContain('Next page (iOS): --after cursor-ios');
|
||||
});
|
||||
});
|
||||
|
||||
describe(buildObserveNavigationRoutesJson, () => {
|
||||
it('maps routes to the requested metrics and stats, including pageInfoByPlatform', () => {
|
||||
const routes = [makeRoute('/home', AppPlatform.Ios)];
|
||||
const pageInfoByPlatform = new Map([
|
||||
[AppPlatform.Ios, { hasNextPage: true, endCursor: 'cursor-ios' }],
|
||||
]);
|
||||
|
||||
const result = buildObserveNavigationRoutesJson(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr', 'expo.navigation.tti'],
|
||||
['median', 'p90', 'count'],
|
||||
pageInfoByPlatform
|
||||
);
|
||||
|
||||
expect(result.routes).toEqual([
|
||||
{
|
||||
routeName: '/home',
|
||||
platform: AppPlatform.Ios,
|
||||
metrics: {
|
||||
'expo.navigation.cold_ttr': { median: 0.5, p90: 0.9, count: 10 },
|
||||
'expo.navigation.tti': { median: 0.45, p90: 0.7, count: 12 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(result.pageInfoByPlatform).toEqual({
|
||||
[AppPlatform.Ios]: { hasNextPage: true, endCursor: 'cursor-ios' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for stats when the underlying value is null', () => {
|
||||
const routes = [
|
||||
makeRoute('/home', AppPlatform.Ios, {
|
||||
coldTtr: { count: 0, median: null, p90: null },
|
||||
}),
|
||||
];
|
||||
|
||||
const result = buildObserveNavigationRoutesJson(
|
||||
routes,
|
||||
['expo.navigation.cold_ttr'],
|
||||
['median', 'p90', 'count'],
|
||||
new Map()
|
||||
);
|
||||
|
||||
expect(result.routes[0].metrics['expo.navigation.cold_ttr']).toEqual({
|
||||
median: null,
|
||||
p90: null,
|
||||
count: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,8 @@
|
||||
import { getMetricDisplayName, resolveMetricName } from '../metricNames';
|
||||
import {
|
||||
getMetricDisplayName,
|
||||
resolveMetricName,
|
||||
resolveNavigationMetricName,
|
||||
} from '../metricNames';
|
||||
|
||||
describe(resolveMetricName, () => {
|
||||
it('resolves short alias "tti" to full metric name', () => {
|
||||
@@ -37,15 +41,46 @@ describe(resolveMetricName, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveNavigationMetricName, () => {
|
||||
it('resolves short aliases to navigation metric full names', () => {
|
||||
expect(resolveNavigationMetricName('cold_ttr')).toBe('expo.navigation.cold_ttr');
|
||||
expect(resolveNavigationMetricName('warm_ttr')).toBe('expo.navigation.warm_ttr');
|
||||
expect(resolveNavigationMetricName('nav_tti')).toBe('expo.navigation.tti');
|
||||
});
|
||||
|
||||
it('passes through full navigation metric names unchanged', () => {
|
||||
expect(resolveNavigationMetricName('expo.navigation.cold_ttr')).toBe(
|
||||
'expo.navigation.cold_ttr'
|
||||
);
|
||||
expect(resolveNavigationMetricName('expo.navigation.warm_ttr')).toBe(
|
||||
'expo.navigation.warm_ttr'
|
||||
);
|
||||
expect(resolveNavigationMetricName('expo.navigation.tti')).toBe('expo.navigation.tti');
|
||||
});
|
||||
|
||||
it('throws on unknown alias or non-navigation metric', () => {
|
||||
expect(() => resolveNavigationMetricName('unknown')).toThrow('Unknown navigation metric');
|
||||
expect(() => resolveNavigationMetricName('expo.app_startup.tti')).toThrow(
|
||||
'Unknown navigation metric'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(getMetricDisplayName, () => {
|
||||
it('returns short display name for known metrics', () => {
|
||||
it('returns short display name for known app-startup metrics', () => {
|
||||
expect(getMetricDisplayName('expo.app_startup.cold_launch_time')).toBe('Cold Launch');
|
||||
expect(getMetricDisplayName('expo.app_startup.warm_launch_time')).toBe('Warm Launch');
|
||||
expect(getMetricDisplayName('expo.app_startup.tti')).toBe('TTI');
|
||||
expect(getMetricDisplayName('expo.app_startup.ttr')).toBe('TTR');
|
||||
expect(getMetricDisplayName('expo.app_startup.tti')).toBe('Startup TTI');
|
||||
expect(getMetricDisplayName('expo.app_startup.ttr')).toBe('Startup TTR');
|
||||
expect(getMetricDisplayName('expo.app_startup.bundle_load_time')).toBe('Bundle Load');
|
||||
});
|
||||
|
||||
it('returns short display name for known navigation metrics', () => {
|
||||
expect(getMetricDisplayName('expo.navigation.cold_ttr')).toBe('Nav Cold TTR');
|
||||
expect(getMetricDisplayName('expo.navigation.warm_ttr')).toBe('Nav Warm TTR');
|
||||
expect(getMetricDisplayName('expo.navigation.tti')).toBe('Nav TTI');
|
||||
});
|
||||
|
||||
it('returns the full metric name for unknown metrics', () => {
|
||||
expect(getMetricDisplayName('custom.metric.name')).toBe('custom.metric.name');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasCommandError } from '../commandUtils/errors';
|
||||
import { AppObservePlatform, AppPlatform } from '../graphql/generated';
|
||||
import { AppPlatform } from '../graphql/generated';
|
||||
import { ObserveQuery } from '../graphql/queries/ObserveQuery';
|
||||
import Log from '../log';
|
||||
import {
|
||||
@@ -10,11 +10,7 @@ import {
|
||||
UpdateIdsMap,
|
||||
makeMetricsKey,
|
||||
} from './formatMetrics';
|
||||
|
||||
const appPlatformToObservePlatform: Record<AppPlatform, AppObservePlatform> = {
|
||||
[AppPlatform.Android]: AppObservePlatform.Android,
|
||||
[AppPlatform.Ios]: AppObservePlatform.Ios,
|
||||
};
|
||||
import { appPlatformToObservePlatform } from './platforms';
|
||||
|
||||
export function validateDateFlag(value: string, flagName: string): void {
|
||||
const parsed = new Date(value);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
AppObserveEventsOrderByDirection,
|
||||
AppObserveNavigationRoute,
|
||||
AppObserveNavigationRoutesOrderBy,
|
||||
AppObserveNavigationRoutesOrderByField,
|
||||
AppPlatform,
|
||||
PageInfo,
|
||||
} from '../graphql/generated';
|
||||
import { ObserveQuery } from '../graphql/queries/ObserveQuery';
|
||||
import Log from '../log';
|
||||
import { appPlatformToObservePlatform } from './platforms';
|
||||
|
||||
export interface NavigationRouteWithPlatform {
|
||||
platform: AppPlatform;
|
||||
route: AppObserveNavigationRoute;
|
||||
}
|
||||
|
||||
export interface FetchNavigationRoutesOptions {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
platforms: AppPlatform[];
|
||||
limit: number;
|
||||
after?: string;
|
||||
appVersion?: string;
|
||||
updateId?: string;
|
||||
buildNumber?: string;
|
||||
orderBy?: AppObserveNavigationRoutesOrderBy;
|
||||
}
|
||||
|
||||
export interface FetchNavigationRoutesResult {
|
||||
routes: NavigationRouteWithPlatform[];
|
||||
pageInfoByPlatform: Map<AppPlatform, PageInfo>;
|
||||
}
|
||||
|
||||
export async function fetchObserveNavigationRoutesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appId: string,
|
||||
options: FetchNavigationRoutesOptions
|
||||
): Promise<FetchNavigationRoutesResult> {
|
||||
const orderBy: AppObserveNavigationRoutesOrderBy = options.orderBy ?? {
|
||||
field: AppObserveNavigationRoutesOrderByField.NavigationCount,
|
||||
direction: AppObserveEventsOrderByDirection.Desc,
|
||||
};
|
||||
|
||||
const queries = options.platforms.map(async appPlatform => {
|
||||
const observePlatform = appPlatformToObservePlatform[appPlatform];
|
||||
try {
|
||||
const result = await ObserveQuery.navigationRoutesAsync(graphqlClient, {
|
||||
appId,
|
||||
filter: {
|
||||
platform: observePlatform,
|
||||
startTime: options.startTime,
|
||||
endTime: options.endTime,
|
||||
...(options.appVersion && { appVersion: options.appVersion }),
|
||||
...(options.updateId && { appUpdateId: options.updateId }),
|
||||
...(options.buildNumber && { appBuildNumber: options.buildNumber }),
|
||||
},
|
||||
first: options.limit,
|
||||
...(options.after && { after: options.after }),
|
||||
orderBy,
|
||||
});
|
||||
return { appPlatform, ...result };
|
||||
} catch (error: any) {
|
||||
Log.warn(`Failed to fetch navigation routes on ${observePlatform}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(queries);
|
||||
|
||||
const routes: NavigationRouteWithPlatform[] = [];
|
||||
const pageInfoByPlatform = new Map<AppPlatform, PageInfo>();
|
||||
|
||||
for (const result of results) {
|
||||
if (!result) {
|
||||
continue;
|
||||
}
|
||||
pageInfoByPlatform.set(result.appPlatform, result.pageInfo);
|
||||
for (const route of result.routes) {
|
||||
routes.push({ platform: result.appPlatform, route });
|
||||
}
|
||||
}
|
||||
|
||||
return { routes, pageInfoByPlatform };
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import { allowedPlatformFlagValues } from './platforms';
|
||||
|
||||
export const ObserveProjectIdFlag = {
|
||||
'project-id': Flags.string({
|
||||
description: 'EAS project ID (defaults to the project ID of the current directory)',
|
||||
}),
|
||||
};
|
||||
|
||||
export const ObservePlatformFlag = {
|
||||
platform: Flags.option({
|
||||
description: 'Filter by platform',
|
||||
options: allowedPlatformFlagValues,
|
||||
})(),
|
||||
};
|
||||
|
||||
export const ObserveTimeRangeFlags = {
|
||||
start: Flags.string({
|
||||
description: 'Start of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
end: Flags.string({
|
||||
description: 'End of time range (ISO date)',
|
||||
exclusive: ['days'],
|
||||
}),
|
||||
days: Flags.integer({
|
||||
description: 'Show results from the last N days (mutually exclusive with --start/--end)',
|
||||
min: 1,
|
||||
exclusive: ['start', 'end'],
|
||||
}),
|
||||
};
|
||||
|
||||
export const ObserveAfterFlag = {
|
||||
after: Flags.string({
|
||||
description:
|
||||
'Cursor for pagination. Use the endCursor from a previous query to fetch the next page.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const ObserveAppVersionFlag = {
|
||||
'app-version': Flags.string({
|
||||
description: 'Filter by app version',
|
||||
}),
|
||||
};
|
||||
|
||||
export const ObserveUpdateIdFlag = {
|
||||
'update-id': Flags.string({
|
||||
description: 'Filter by EAS update ID',
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { EasCommandError } from '../commandUtils/errors';
|
||||
import { AppPlatform } from '../graphql/generated';
|
||||
import { appPlatformDisplayNames } from '../platform';
|
||||
import renderTextTable from '../utils/renderTextTable';
|
||||
import { NavigationRouteWithPlatform } from './fetchNavigationRoutes';
|
||||
import { buildTimeRangeDescription } from './formatUtils';
|
||||
import { getMetricDisplayName } from './metricNames';
|
||||
|
||||
export type NavigationStatKey = 'median' | 'p90' | 'count';
|
||||
|
||||
export const NAVIGATION_STAT_ALIASES: Record<string, NavigationStatKey> = {
|
||||
med: 'median',
|
||||
median: 'median',
|
||||
p90: 'p90',
|
||||
count: 'count',
|
||||
event_count: 'count',
|
||||
eventCount: 'count',
|
||||
};
|
||||
|
||||
export const NAVIGATION_STAT_DISPLAY_NAMES: Record<NavigationStatKey, string> = {
|
||||
median: 'Med',
|
||||
p90: 'P90',
|
||||
count: 'Count',
|
||||
};
|
||||
|
||||
export function resolveNavigationStatKey(input: string): NavigationStatKey {
|
||||
const resolved = NAVIGATION_STAT_ALIASES[input];
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
throw new EasCommandError(
|
||||
`Unknown statistic: "${input}". Valid options: ${Object.keys(NAVIGATION_STAT_ALIASES).join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
type NavigationRouteField = 'coldTtr' | 'warmTtr' | 'tti';
|
||||
|
||||
const FULL_METRIC_NAME_TO_FIELD: Record<string, NavigationRouteField> = {
|
||||
'expo.navigation.cold_ttr': 'coldTtr',
|
||||
'expo.navigation.warm_ttr': 'warmTtr',
|
||||
'expo.navigation.tti': 'tti',
|
||||
};
|
||||
|
||||
export const NAVIGATION_METRIC_NAMES = Object.keys(FULL_METRIC_NAME_TO_FIELD);
|
||||
|
||||
export function isNavigationMetric(metricName: string): boolean {
|
||||
return metricName in FULL_METRIC_NAME_TO_FIELD;
|
||||
}
|
||||
|
||||
function formatStatValue(stat: NavigationStatKey, value: number | null | undefined): string {
|
||||
if (value == null) {
|
||||
return '-';
|
||||
}
|
||||
if (stat === 'count') {
|
||||
return String(value);
|
||||
}
|
||||
return `${value.toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function formatMergedCell(
|
||||
stat: NavigationStatKey,
|
||||
statValue: number | null | undefined,
|
||||
count: number | null | undefined
|
||||
): string {
|
||||
const formatted = formatStatValue(stat, statValue);
|
||||
const countStr = count != null ? String(count) : '-';
|
||||
return `${formatted} (${countStr})`;
|
||||
}
|
||||
|
||||
export interface NavigationRouteValuesJson {
|
||||
routeName: string;
|
||||
platform: AppPlatform;
|
||||
metrics: Record<string, Partial<Record<NavigationStatKey, number | null>>>;
|
||||
}
|
||||
|
||||
export interface ObserveNavigationRoutesJsonOutput {
|
||||
routes: NavigationRouteValuesJson[];
|
||||
pageInfoByPlatform: Record<string, { hasNextPage: boolean; endCursor: string | null }>;
|
||||
}
|
||||
|
||||
function metricStat(
|
||||
node: NavigationRouteWithPlatform,
|
||||
metricName: string
|
||||
): { count: number; median: number | null; p90: number | null } {
|
||||
const field = FULL_METRIC_NAME_TO_FIELD[metricName];
|
||||
if (!field) {
|
||||
throw new EasCommandError(`Unknown navigation metric: "${metricName}"`);
|
||||
}
|
||||
const stat = node.route[field];
|
||||
return {
|
||||
count: stat.count,
|
||||
median: stat.median ?? null,
|
||||
p90: stat.p90 ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildObserveNavigationRoutesJson(
|
||||
routes: NavigationRouteWithPlatform[],
|
||||
metricNames: string[],
|
||||
stats: NavigationStatKey[],
|
||||
pageInfoByPlatform: Map<AppPlatform, { hasNextPage: boolean; endCursor?: string | null }>
|
||||
): ObserveNavigationRoutesJsonOutput {
|
||||
const jsonRoutes: NavigationRouteValuesJson[] = routes.map(node => {
|
||||
const metrics: Record<string, Partial<Record<NavigationStatKey, number | null>>> = {};
|
||||
for (const metricName of metricNames) {
|
||||
const values = metricStat(node, metricName);
|
||||
const statValues: Partial<Record<NavigationStatKey, number | null>> = {};
|
||||
for (const stat of stats) {
|
||||
statValues[stat] = values[stat] ?? null;
|
||||
}
|
||||
metrics[metricName] = statValues;
|
||||
}
|
||||
return {
|
||||
routeName: node.route.routeName,
|
||||
platform: node.platform,
|
||||
metrics,
|
||||
};
|
||||
});
|
||||
|
||||
const pageInfoByPlatformOutput: Record<
|
||||
string,
|
||||
{ hasNextPage: boolean; endCursor: string | null }
|
||||
> = {};
|
||||
for (const [platform, pageInfo] of pageInfoByPlatform) {
|
||||
pageInfoByPlatformOutput[platform] = {
|
||||
hasNextPage: pageInfo.hasNextPage,
|
||||
endCursor: pageInfo.endCursor ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
return { routes: jsonRoutes, pageInfoByPlatform: pageInfoByPlatformOutput };
|
||||
}
|
||||
|
||||
export interface BuildNavigationRoutesTableOptions {
|
||||
daysBack?: number;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
pageInfoByPlatform?: Map<AppPlatform, { hasNextPage: boolean; endCursor?: string | null }>;
|
||||
}
|
||||
|
||||
export function buildObserveNavigationRoutesTable(
|
||||
routes: NavigationRouteWithPlatform[],
|
||||
metricNames: string[],
|
||||
stats: NavigationStatKey[],
|
||||
options?: BuildNavigationRoutesTableOptions
|
||||
): string {
|
||||
if (routes.length === 0) {
|
||||
return chalk.yellow('No navigation routes found.');
|
||||
}
|
||||
|
||||
const displayStats = stats.filter(s => s !== 'count');
|
||||
const hasCount = stats.includes('count');
|
||||
|
||||
const statsDesc =
|
||||
displayStats.length > 0
|
||||
? displayStats.map(s => NAVIGATION_STAT_DISPLAY_NAMES[s]).join(', ')
|
||||
: 'Navigation count';
|
||||
const timeDesc = buildTimeRangeDescription({
|
||||
daysBack: options?.daysBack,
|
||||
startTime: options?.startTime,
|
||||
endTime: options?.endTime,
|
||||
});
|
||||
const countSuffix = hasCount && displayStats.length > 0 ? ' (navigation count)' : '';
|
||||
const summaryLine = `${statsDesc} values${countSuffix}${timeDesc ? ` ${timeDesc}` : ''}`;
|
||||
|
||||
const byPlatform = new Map<AppPlatform, NavigationRouteWithPlatform[]>();
|
||||
for (const node of routes) {
|
||||
if (!byPlatform.has(node.platform)) {
|
||||
byPlatform.set(node.platform, []);
|
||||
}
|
||||
byPlatform.get(node.platform)!.push(node);
|
||||
}
|
||||
|
||||
const metricHeaders: string[] = [];
|
||||
for (const metricName of metricNames) {
|
||||
const displayName = getMetricDisplayName(metricName);
|
||||
if (displayStats.length > 0 && hasCount) {
|
||||
for (const stat of displayStats) {
|
||||
metricHeaders.push(
|
||||
displayStats.length > 1
|
||||
? `${displayName} ${NAVIGATION_STAT_DISPLAY_NAMES[stat]}`
|
||||
: displayName
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (const stat of displayStats.length > 0
|
||||
? displayStats
|
||||
: (['count'] as NavigationStatKey[])) {
|
||||
metricHeaders.push(
|
||||
displayStats.length === 0
|
||||
? `${displayName} Count`
|
||||
: `${displayName} ${NAVIGATION_STAT_DISPLAY_NAMES[stat]}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const headers = ['Route', ...metricHeaders];
|
||||
|
||||
const sections: string[] = [chalk.bold(summaryLine)];
|
||||
|
||||
for (const [platform, platformRoutes] of byPlatform) {
|
||||
sections.push('');
|
||||
sections.push(chalk.bold(appPlatformDisplayNames[platform]));
|
||||
|
||||
const rows: string[][] = platformRoutes.map(node => {
|
||||
const cells: string[] = [];
|
||||
for (const metricName of metricNames) {
|
||||
const values = metricStat(node, metricName);
|
||||
if (displayStats.length > 0 && hasCount) {
|
||||
for (const stat of displayStats) {
|
||||
cells.push(formatMergedCell(stat, values[stat], values.count));
|
||||
}
|
||||
} else {
|
||||
for (const stat of displayStats.length > 0
|
||||
? displayStats
|
||||
: (['count'] as NavigationStatKey[])) {
|
||||
cells.push(formatStatValue(stat, values[stat]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return [node.route.routeName, ...cells];
|
||||
});
|
||||
|
||||
sections.push(renderTextTable(headers, rows));
|
||||
|
||||
const pageInfo = options?.pageInfoByPlatform?.get(platform);
|
||||
if (pageInfo?.hasNextPage && pageInfo.endCursor) {
|
||||
sections.push(
|
||||
`Next page (${appPlatformDisplayNames[platform]}): --after ${pageInfo.endCursor}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return sections.join('\n');
|
||||
}
|
||||
@@ -8,14 +8,24 @@ export const METRIC_ALIASES: Record<string, string> = {
|
||||
bundle_load: 'expo.app_startup.bundle_load_time',
|
||||
};
|
||||
|
||||
export const NAVIGATION_METRIC_ALIASES: Record<string, string> = {
|
||||
cold_ttr: 'expo.navigation.cold_ttr',
|
||||
warm_ttr: 'expo.navigation.warm_ttr',
|
||||
nav_tti: 'expo.navigation.tti',
|
||||
};
|
||||
|
||||
const KNOWN_FULL_NAMES = new Set(Object.values(METRIC_ALIASES));
|
||||
const KNOWN_FULL_NAVIGATION_NAMES = new Set(Object.values(NAVIGATION_METRIC_ALIASES));
|
||||
|
||||
export const METRIC_SHORT_NAMES: Record<string, string> = {
|
||||
'expo.app_startup.cold_launch_time': 'Cold Launch',
|
||||
'expo.app_startup.warm_launch_time': 'Warm Launch',
|
||||
'expo.app_startup.tti': 'TTI',
|
||||
'expo.app_startup.ttr': 'TTR',
|
||||
'expo.app_startup.tti': 'Startup TTI',
|
||||
'expo.app_startup.ttr': 'Startup TTR',
|
||||
'expo.app_startup.bundle_load_time': 'Bundle Load',
|
||||
'expo.navigation.cold_ttr': 'Nav Cold TTR',
|
||||
'expo.navigation.warm_ttr': 'Nav Warm TTR',
|
||||
'expo.navigation.tti': 'Nav TTI',
|
||||
};
|
||||
|
||||
export function resolveMetricName(input: string): string {
|
||||
@@ -30,6 +40,20 @@ export function resolveMetricName(input: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveNavigationMetricName(input: string): string {
|
||||
if (NAVIGATION_METRIC_ALIASES[input]) {
|
||||
return NAVIGATION_METRIC_ALIASES[input];
|
||||
}
|
||||
if (KNOWN_FULL_NAVIGATION_NAMES.has(input)) {
|
||||
return input;
|
||||
}
|
||||
throw new EasCommandError(
|
||||
`Unknown navigation metric: "${input}". Use a full metric name (e.g. expo.navigation.cold_ttr) or a short alias: ${Object.keys(
|
||||
NAVIGATION_METRIC_ALIASES
|
||||
).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
export function getMetricDisplayName(metricName: string): string {
|
||||
return METRIC_SHORT_NAMES[metricName] ?? metricName;
|
||||
}
|
||||
@@ -51,3 +51,8 @@ export function appPlatformsFromFlag(flag: PlatformFlagValue | undefined): AppPl
|
||||
}
|
||||
return [defaultAppPlatform];
|
||||
}
|
||||
|
||||
export const appPlatformToObservePlatform: Record<AppPlatform, AppObservePlatform> = {
|
||||
[AppPlatform.Android]: AppObservePlatform.Android,
|
||||
[AppPlatform.Ios]: AppObservePlatform.Ios,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/worker",
|
||||
"version": "18.12.3",
|
||||
"version": "18.13.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "yarn watch",
|
||||
|
||||
@@ -1,75 +1,12 @@
|
||||
import {
|
||||
ArchiveSourceType,
|
||||
BuildMode,
|
||||
BuildTrigger,
|
||||
Platform,
|
||||
Workflow,
|
||||
errors,
|
||||
} from '@expo/eas-build-job';
|
||||
import { errors } from '@expo/eas-build-job';
|
||||
import spawn from '@expo/turtle-spawn';
|
||||
import { vol } from 'memfs';
|
||||
import path from 'path';
|
||||
|
||||
import { build } from '../build';
|
||||
import { createBuildContext } from '../context';
|
||||
import BuildService, { getExpoPackageVersionAsync } from '../service';
|
||||
import { turtleFetch } from '../utils/turtleFetch';
|
||||
import { getExpoPackageVersionAsync } from '../service';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/turtle-spawn', () => jest.fn());
|
||||
jest.mock('../build', () => ({
|
||||
build: jest.fn(),
|
||||
}));
|
||||
jest.mock('../context', () => ({
|
||||
createBuildContext: jest.fn(),
|
||||
}));
|
||||
jest.mock('../external/analytics', () => ({
|
||||
Analytics: jest.fn().mockImplementation(() => ({
|
||||
flushEventsAsync: jest.fn(),
|
||||
logEvent: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
jest.mock('../logger', () => {
|
||||
const logger = {
|
||||
child: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
level: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
};
|
||||
logger.child.mockReturnValue(logger);
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: logger,
|
||||
createBuildLoggerWithSecretsFilter: jest.fn(async () => ({
|
||||
cleanUp: jest.fn(),
|
||||
logBuffer: { getLogs: jest.fn(() => []), getPhaseLogs: jest.fn(() => []) },
|
||||
logger,
|
||||
})),
|
||||
};
|
||||
});
|
||||
jest.mock('../sentry', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
capture: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('../utils/turtleFetch', () => ({
|
||||
turtleFetch: jest.fn(),
|
||||
}));
|
||||
jest.mock('../config', () => {
|
||||
const config = jest.requireActual('../config').default;
|
||||
return {
|
||||
__esModule: true,
|
||||
default: {
|
||||
...config,
|
||||
buildId: 'build-id',
|
||||
wwwApiV2BaseUrl: 'http://api.expo.test/v2/',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe(getExpoPackageVersionAsync, () => {
|
||||
const projectRoot = '/test-project';
|
||||
@@ -122,136 +59,3 @@ describe(getExpoPackageVersionAsync, () => {
|
||||
await expect(getExpoPackageVersionAsync(buildContext)).rejects.toBeInstanceOf(errors.UserError);
|
||||
});
|
||||
});
|
||||
|
||||
describe(BuildService, () => {
|
||||
const projectRoot = '/test-project';
|
||||
const job = {
|
||||
appId: 'app-id',
|
||||
cache: {
|
||||
clear: false,
|
||||
disabled: false,
|
||||
paths: [],
|
||||
},
|
||||
mode: BuildMode.BUILD,
|
||||
platform: Platform.IOS,
|
||||
projectArchive: {
|
||||
type: ArchiveSourceType.URL,
|
||||
url: 'https://example.com/project.tar.gz',
|
||||
},
|
||||
projectRootDirectory: '.',
|
||||
secrets: {
|
||||
robotAccessToken: 'robot-token',
|
||||
},
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
type: Workflow.GENERIC,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
jest.clearAllMocks();
|
||||
jest.mocked(spawn).mockResolvedValue({
|
||||
stdout: Buffer.from(path.join(projectRoot, 'node_modules/expo/package.json')),
|
||||
} as any);
|
||||
jest.mocked(turtleFetch).mockResolvedValue({ ok: true } as any);
|
||||
jest.mocked(build).mockRejectedValue(new Error('raw build failure'));
|
||||
jest.mocked(createBuildContext).mockReturnValue({
|
||||
env: { PATH: '/usr/bin' },
|
||||
getReactNativeProjectDirectory: () => projectRoot,
|
||||
job,
|
||||
logger: {},
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('sends expo_package_version to Datadog error logs without changing sdk_version', async () => {
|
||||
vol.fromJSON({
|
||||
[path.join(projectRoot, 'node_modules/expo/package.json')]: JSON.stringify({
|
||||
version: '55.0.17',
|
||||
}),
|
||||
});
|
||||
const service = new BuildService();
|
||||
jest.spyOn(service, 'finishError').mockResolvedValue(undefined);
|
||||
|
||||
await (service as any).startBuildInternal({
|
||||
initiatingUserId: 'user-id',
|
||||
job,
|
||||
metadata: {
|
||||
buildProfile: 'production',
|
||||
reactNativeVersion: '0.83.0',
|
||||
sdkVersion: '55.0.0',
|
||||
},
|
||||
projectId: 'project-id',
|
||||
});
|
||||
|
||||
expect(turtleFetch).toHaveBeenCalledWith(
|
||||
'http://api.expo.test/v2/turtle-builds/logs',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
json: expect.objectContaining({
|
||||
tags: expect.objectContaining({
|
||||
expo_package_version: '55.0.17',
|
||||
sdk_version: '55.0.0',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('sends null expo_package_version to Datadog error logs when expo package version resolution fails', async () => {
|
||||
jest.mocked(spawn).mockRejectedValue(new Error('Cannot find module expo/package.json'));
|
||||
const service = new BuildService();
|
||||
jest.spyOn(service, 'finishError').mockResolvedValue(undefined);
|
||||
|
||||
await (service as any).startBuildInternal({
|
||||
initiatingUserId: 'user-id',
|
||||
job,
|
||||
metadata: {
|
||||
sdkVersion: '55.0.0',
|
||||
},
|
||||
projectId: 'project-id',
|
||||
});
|
||||
|
||||
expect(turtleFetch).toHaveBeenCalledWith(
|
||||
'http://api.expo.test/v2/turtle-builds/logs',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
json: expect.objectContaining({
|
||||
tags: expect.objectContaining({
|
||||
expo_package_version: null,
|
||||
sdk_version: '55.0.0',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not try to resolve expo_package_version when build context setup fails', async () => {
|
||||
jest.mocked(createBuildContext).mockImplementation(() => {
|
||||
throw new Error('context setup failure');
|
||||
});
|
||||
const service = new BuildService();
|
||||
jest.spyOn(service, 'finishError').mockResolvedValue(undefined);
|
||||
|
||||
await (service as any).startBuildInternal({
|
||||
initiatingUserId: 'user-id',
|
||||
job,
|
||||
metadata: {
|
||||
sdkVersion: '55.0.0',
|
||||
},
|
||||
projectId: 'project-id',
|
||||
});
|
||||
|
||||
expect(spawn).not.toHaveBeenCalled();
|
||||
expect(turtleFetch).toHaveBeenCalledWith(
|
||||
'http://api.expo.test/v2/turtle-builds/logs',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
json: expect.objectContaining({
|
||||
tags: expect.objectContaining({
|
||||
expo_package_version: null,
|
||||
sdk_version: '55.0.0',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -34,7 +34,6 @@ import logger, { createBuildLoggerWithSecretsFilter } from './logger';
|
||||
import sentry from './sentry';
|
||||
import State from './state';
|
||||
import { WebSocketServer } from './utils/WebSocketServer';
|
||||
import { turtleFetch } from './utils/turtleFetch';
|
||||
|
||||
export const HANGING_WORKER_CHECK_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
@@ -334,60 +333,6 @@ export default class BuildService {
|
||||
},
|
||||
});
|
||||
|
||||
const robotAccessToken = job.secrets?.robotAccessToken;
|
||||
if (robotAccessToken) {
|
||||
const expoPackageVersionResult = this.buildContext
|
||||
? await asyncResult(getExpoPackageVersionAsync(this.buildContext))
|
||||
: null;
|
||||
const expoPackageVersion =
|
||||
expoPackageVersionResult?.ok === true ? expoPackageVersionResult.value : null;
|
||||
let rawErrorMessage: string = '';
|
||||
if (maybeRawError?.stderr) {
|
||||
rawErrorMessage += '\n' + getLastNLines(100, maybeRawError.stderr);
|
||||
}
|
||||
if (maybeRawError?.stdout) {
|
||||
rawErrorMessage += '\n' + getLastNLines(100, maybeRawError.stdout);
|
||||
}
|
||||
if (!rawErrorMessage) {
|
||||
rawErrorMessage = maybeRawError?.message ?? err.message;
|
||||
}
|
||||
|
||||
try {
|
||||
await turtleFetch(
|
||||
new URL('turtle-builds/logs', config.wwwApiV2BaseUrl).toString(),
|
||||
'POST',
|
||||
{
|
||||
json: {
|
||||
buildId: this.buildId,
|
||||
message: rawErrorMessage,
|
||||
level: 'error',
|
||||
tags: {
|
||||
build_phase: err.buildPhase ?? null,
|
||||
error_code: err.errorCode,
|
||||
platform: job.platform,
|
||||
workflow: job.type,
|
||||
sdk_version: metadata?.sdkVersion ?? null,
|
||||
expo_package_version: expoPackageVersion,
|
||||
react_native_version: metadata?.reactNativeVersion ?? null,
|
||||
app_id: job.appId ?? null,
|
||||
build_profile: metadata?.buildProfile ?? null,
|
||||
app_name: metadata?.appName ?? null,
|
||||
app_identifier: metadata?.appIdentifier ?? null,
|
||||
distribution: metadata?.distribution ?? null,
|
||||
cli_version: metadata?.cliVersion ?? null,
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${robotAccessToken}`,
|
||||
},
|
||||
shouldThrowOnNotOk: false,
|
||||
}
|
||||
);
|
||||
} catch (fetchError: any) {
|
||||
logger.warn({ err: fetchError }, 'Failed to send build error log');
|
||||
}
|
||||
}
|
||||
|
||||
await this.finishError(err, maybeArtifacts);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user