Compare commits

..
14 Commits
Author SHA1 Message Date
Dominik Sokal 9201af566d v2.4.1 2022-10-17 10:57:02 +02:00
Dominik Sokal cf4eb7a4e4 [ENG-6721][submit] move error messages from CLI (#1443)
* remove submit errors from CLI

* update CHANGELOG.md
2022-10-17 09:45:53 +02:00
Dominik Sokal 3ca02ab740 update CHANGELOG.md 2022-10-14 14:36:43 +02:00
Dominik Sokal c05ef9ccca upgrade eas-cli-local-build-plugin to 0.0.114 2022-10-14 14:33:52 +02:00
Expo CI dccd249cb0 update CHANGELOG.md 2022-10-14 08:03:48 +00:00
Dominik Sokal 9e3840a987 v2.4.0 2022-10-14 09:55:46 +02:00
Dominik Sokal a32e98a72a handle new EAS Submit errors (#1440)
* handle new EAS Submit errors

* update CHANGELOG.md
2022-10-14 09:47:02 +02:00
Will Schurman 3496c30a2c Revert "[eas-cli] Fix command statistic analytics (#1439)" (#1442)
This reverts commit 38e4b65a8a.
2022-10-13 18:11:50 -07:00
Will Schurman 38e4b65a8a [eas-cli] Fix command statistic analytics (#1439) 2022-10-13 17:17:36 -07:00
Dominik Sokal 5e5273e6a4 [ENG-6652] automatically choose next version when releasing (#1433)
* automatically choose next version when releasing

* Update CONTRIBUTING.md
2022-10-13 09:23:27 +02:00
Will Schurman e1be834d62 [eas-cli] Update changelog (#1438) 2022-10-12 10:16:03 -07:00
Will Schurman 55e5ab465a [25/n][eas-cli] Move graphql and session into context (#1436) 2022-10-12 10:12:36 -07:00
Will Schurman 5be66f3963 [24/n][eas-cli] Start to move graphql into a context (#1435) 2022-10-12 10:11:48 -07:00
Expo CI 024c699637 update CHANGELOG.md 2022-10-10 11:42:55 +00:00
232 changed files with 3247 additions and 2096 deletions

No files matched your search

+20 -2
View File
@@ -8,12 +8,30 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Add new experimental resource class for M1 Macs. ([#1425](https://github.com/expo/eas-cli/pull/1425) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
- Bump `eas-cli-local-build-plugin` to 0.0.144 to not require `yarn` for local builds. ([c05ef](https://github.com/expo/eas-cli/commit/c05ef9cccaa819d8e5fb84858cb09cadaae90134) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Remove EAS Submit errors from codebase. The errors have been moved to the server side. ([#1443](https://github.com/expo/eas-cli/pull/1443) by [@dsokal](https://github.com/dsokal))
## [2.4.0](https://github.com/expo/eas-cli/releases/tag/v2.4.0) - 2022-10-14
### 🎉 New features
- Handle more common errors for EAS Submit. ([#1440](https://github.com/expo/eas-cli/pull/1440) by [@dsokal](https://github.com/dsokal))
### 🧹 Chores
- Move graphql and session into command context. ([#1435](https://github.com/expo/eas-cli/pull/1435), [#1436](https://github.com/expo/eas-cli/pull/1436) by [@wschurman](https://github.com/wschurman))
## [2.3.0](https://github.com/expo/eas-cli/releases/tag/v2.3.0) - 2022-10-10
### 🎉 New features
- Add new experimental resource class for M1 Macs. ([#1425](https://github.com/expo/eas-cli/pull/1425) by [@szdziedzic](https://github.com/szdziedzic))
## [2.2.1](https://github.com/expo/eas-cli/releases/tag/v2.2.1) - 2022-10-05
### 🐛 Bug fixes
+1 -1
View File
@@ -1,4 +1,4 @@
## Releasing a new version
1. Run `yarn release` in the repository root folder.
1. Run `yarn release` in the repository root folder. The next version is chosen automatically based on the changelog entries. If you want to use different version, pass the version string as a positional argument to the command, e.g. `yarn release 1.2.3`.
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "2.3.0"
"version": "2.4.1"
}
+1 -1
View File
@@ -12,7 +12,7 @@
"watch": "yarn start",
"eas": "packages/eas-cli/bin/run",
"lint": "eslint .",
"release": "./scripts/bin/run update-local-plugin && lerna version --exact",
"release": "./scripts/bin/run release",
"test": "jest",
"clean": "lerna run clean && rm -rf node_modules coverage"
},
+47 -47
View File
@@ -125,7 +125,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -142,7 +142,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -159,7 +159,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -173,7 +173,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -223,7 +223,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -244,7 +244,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -264,7 +264,7 @@ DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -284,7 +284,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -307,7 +307,7 @@ DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -337,7 +337,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -354,7 +354,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -371,7 +371,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -406,7 +406,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -443,7 +443,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:submit`
@@ -490,7 +490,7 @@ DESCRIPTION
Update version of an app.
```
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -509,7 +509,7 @@ DESCRIPTION
Update a version in native code with a value stored on EAS servers
```
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -526,7 +526,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -547,7 +547,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -569,7 +569,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -589,7 +589,7 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:view [NAME]`
@@ -612,7 +612,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -631,7 +631,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -648,7 +648,7 @@ DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -662,7 +662,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -682,7 +682,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -703,7 +703,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -717,7 +717,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -731,7 +731,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -818,7 +818,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -835,7 +835,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open`
@@ -849,7 +849,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/open.ts)_
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -863,7 +863,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -885,7 +885,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -908,7 +908,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -926,7 +926,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -940,7 +940,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit`
@@ -970,7 +970,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1000,7 +1000,7 @@ DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1017,7 +1017,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1038,7 +1038,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1060,7 +1060,7 @@ DESCRIPTION
view the recent updates
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:view GROUPID`
@@ -1080,7 +1080,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1101,7 +1101,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1121,7 +1121,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1138,7 +1138,7 @@ DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1160,7 +1160,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1177,7 +1177,7 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.3.0/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.4.1/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "2.3.0",
"version": "2.4.1",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
+23
View File
@@ -0,0 +1,23 @@
import { JSONValue } from '@expo/json-file';
export class ApiV2Error extends Error {
override readonly name = 'ApiV2Error';
readonly expoApiV2ErrorCode: string;
readonly expoApiV2ErrorDetails?: JSONValue;
readonly expoApiV2ErrorServerStack?: string;
readonly expoApiV2ErrorMetadata?: object;
constructor(response: {
message: string;
code: string;
stack?: string;
details?: JSONValue;
metadata?: object;
}) {
super(response.message);
this.expoApiV2ErrorCode = response.code;
this.expoApiV2ErrorDetails = response.details;
this.expoApiV2ErrorServerStack = response.stack;
this.expoApiV2ErrorMetadata = response.metadata;
}
}
+9 -4
View File
@@ -1,9 +1,10 @@
import assert from 'assert';
import nock from 'nock';
import { ApiV2Error, api, getExpoApiBaseUrl } from '../api';
import { ApiV2Error } from '../ApiV2Error';
import { ApiV2Client, getExpoApiBaseUrl } from '../api';
describe('apiClient', () => {
describe(ApiV2Client, () => {
it('converts Expo APIv2 error to ApiV2Error', async () => {
nock(getExpoApiBaseUrl())
.post('/v2/test')
@@ -21,7 +22,9 @@ describe('apiClient', () => {
let error: Error | null = null;
try {
await api.postAsync('test', { body: {} });
await new ApiV2Client({ accessToken: null, sessionSecret: null }).postAsync('test', {
body: {},
});
} catch (e: any) {
error = e;
}
@@ -41,7 +44,9 @@ describe('apiClient', () => {
let error: Error | null = null;
try {
await api.postAsync('test', { body: {} });
await new ApiV2Client({ accessToken: null, sessionSecret: null }).postAsync('test', {
body: {},
});
} catch (e: any) {
error = e;
}
+59 -74
View File
@@ -1,93 +1,78 @@
import { JSONValue } from '@expo/json-file';
import { ApiV2Error } from './ApiV2Error';
import fetch, { RequestError, RequestInit } from './fetch';
import { getAccessToken, getSessionSecret } from './user/sessionStorage';
export class ApiV2Error extends Error {
override readonly name = 'ApiV2Error';
readonly expoApiV2ErrorCode: string;
readonly expoApiV2ErrorDetails?: JSONValue;
readonly expoApiV2ErrorServerStack?: string;
readonly expoApiV2ErrorMetadata?: object;
constructor(response: {
message: string;
code: string;
stack?: string;
details?: JSONValue;
metadata?: object;
}) {
super(response.message);
this.expoApiV2ErrorCode = response.code;
this.expoApiV2ErrorDetails = response.details;
this.expoApiV2ErrorServerStack = response.stack;
this.expoApiV2ErrorMetadata = response.metadata;
}
}
interface RequestOptions {
body: JSONValue;
}
export const api = {
async putAsync(path: string, options: RequestOptions): Promise<any> {
return await requestAsync(path, { method: 'PUT', body: JSON.stringify(options.body) });
},
export class ApiV2Client {
constructor(
private readonly authInfo: {
accessToken: string | null;
sessionSecret: string | null;
}
) {}
async postAsync(path: string, options: RequestOptions): Promise<any> {
return await requestAsync(path, { method: 'POST', body: JSON.stringify(options.body) });
},
async deleteAsync(path: string): Promise<any> {
return await requestAsync(path, { method: 'DELETE' });
},
async getAsync(path: string): Promise<any> {
return await requestAsync(path, { method: 'GET' });
},
};
async function requestAsync(path: string, options: RequestInit): Promise<any> {
try {
const response = await fetch(`${getExpoApiBaseUrl()}/v2/${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...getAuthHeaders(),
},
});
return await response.json();
} catch (err) {
await handleApiErrorAsync(err);
public async putAsync(path: string, options: RequestOptions): Promise<any> {
return await this.requestAsync(path, { method: 'PUT', body: JSON.stringify(options.body) });
}
}
async function handleApiErrorAsync(err: any): Promise<void> {
if (err instanceof RequestError) {
let result: { [key: string]: any };
public async postAsync(path: string, options: RequestOptions): Promise<any> {
return await this.requestAsync(path, { method: 'POST', body: JSON.stringify(options.body) });
}
public async deleteAsync(path: string): Promise<any> {
return await this.requestAsync(path, { method: 'DELETE' });
}
public async getAsync(path: string): Promise<any> {
return await this.requestAsync(path, { method: 'GET' });
}
private getAuthHeaders(): Record<string, string> {
const token = this.authInfo.accessToken;
if (token) {
return { authorization: `Bearer ${token}` };
}
const sessionSecret = this.authInfo.sessionSecret;
if (sessionSecret) {
return { 'expo-session': sessionSecret };
}
return {};
}
private async requestAsync(path: string, options: RequestInit): Promise<any> {
try {
result = (await err.response.json()) as { [key: string]: any };
} catch {
throw new Error(`Malformed api response: ${await err.response.text()}`);
const response = await fetch(`${getExpoApiBaseUrl()}/v2/${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...this.getAuthHeaders(),
},
});
return await response.json();
} catch (err) {
await ApiV2Client.handleApiErrorAsync(err);
}
if (result.errors?.length) {
throw new ApiV2Error(result.errors[0]);
}
} else {
throw err;
}
}
function getAuthHeaders(): Record<string, string> {
const token = getAccessToken();
if (token) {
return { authorization: `Bearer ${token}` };
private static async handleApiErrorAsync(err: any): Promise<void> {
if (err instanceof RequestError) {
let result: { [key: string]: any };
try {
result = (await err.response.json()) as { [key: string]: any };
} catch {
throw new Error(`Malformed api response: ${await err.response.text()}`);
}
if (result.errors?.length) {
throw new ApiV2Error(result.errors[0]);
}
} else {
throw err;
}
}
const sessionSecret = getSessionSecret();
if (sessionSecret) {
return { 'expo-session': sessionSecret };
}
return {};
}
export function getExpoApiBaseUrl(): string {
+32 -21
View File
@@ -1,6 +1,7 @@
import chalk from 'chalk';
import CliTable from 'cli-table3';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { UpdateBranchFragment } from '../graphql/generated';
import { BranchQuery } from '../graphql/queries/BranchQuery';
@@ -14,17 +15,20 @@ import {
export const BRANCHES_LIMIT = 50;
export async function selectBranchOnAppAsync({
projectId,
promptTitle,
displayTextForListItem,
paginatedQueryOptions,
}: {
projectId: string;
displayTextForListItem: (queryItem: UpdateBranchFragment) => string;
promptTitle: string;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<UpdateBranchFragment> {
export async function selectBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
promptTitle,
displayTextForListItem,
paginatedQueryOptions,
}: {
projectId: string;
displayTextForListItem: (queryItem: UpdateBranchFragment) => string;
promptTitle: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<UpdateBranchFragment> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a branch in non-interactive mode.');
}
@@ -32,7 +36,8 @@ export async function selectBranchOnAppAsync({
const selectedBranch = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) => queryBranchesOnProjectAsync(limit, offset, projectId),
queryToPerform: (limit, offset) =>
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
promptOptions: {
title: promptTitle,
getIdentifierForQueryItem: updateBranchFragment => updateBranchFragment.id,
@@ -45,15 +50,19 @@ export async function selectBranchOnAppAsync({
return selectedBranch;
}
export async function listAndRenderBranchesOnAppAsync({
projectId,
paginatedQueryOptions,
}: {
projectId: string;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<void> {
export async function listAndRenderBranchesOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
paginatedQueryOptions,
}: {
projectId: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<void> {
if (paginatedQueryOptions.nonInteractive) {
const branches = await queryBranchesOnProjectAsync(
graphqlClient,
paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
paginatedQueryOptions.offset,
projectId
@@ -63,7 +72,8 @@ export async function listAndRenderBranchesOnAppAsync({
await paginatedQueryWithConfirmPromptAsync({
limit: paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) => queryBranchesOnProjectAsync(limit, offset, projectId),
queryToPerform: (limit, offset) =>
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
promptOptions: {
title: 'Load more branches?',
renderListItems: branches => renderPageOfBranches(branches, paginatedQueryOptions),
@@ -73,11 +83,12 @@ export async function listAndRenderBranchesOnAppAsync({
}
async function queryBranchesOnProjectAsync(
graphqlClient: ExpoGraphqlClient,
limit: number,
offset: number,
projectId: string
): Promise<UpdateBranchFragment[]> {
return await BranchQuery.listBranchesOnAppAsync({
return await BranchQuery.listBranchesOnAppAsync(graphqlClient, {
appId: projectId,
limit,
offset,
@@ -1,5 +1,7 @@
import { instance, mock } from 'ts-mockito';
import { v4 as uuid } from 'uuid';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { selectBuildToCancelAsync } from '../../commands/build/cancel';
import { AppPlatform, BuildFragment, BuildPriority, BuildStatus } from '../../graphql/generated';
import { BuildQuery } from '../../graphql/queries/BuildQuery';
@@ -43,14 +45,18 @@ describe(selectBuildToCancelAsync.name, () => {
});
it('does not return build id when confirmation is rejected', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
jest.mocked(confirmAsync).mockResolvedValueOnce(false);
expect(selectBuildToCancelAsync(projectId, 'blah')).resolves.toEqual(null);
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
});
it('returns build id when confirmation is confirmed', async () => {
const graphqlClient = instance(mock<ExpoGraphqlClient>());
jest.mocked(selectAsync).mockResolvedValueOnce(selectedBuildId);
jest.mocked(confirmAsync).mockResolvedValueOnce(true);
expect(selectBuildToCancelAsync(projectId, 'blah')).resolves.toEqual(selectedBuildId);
expect(selectBuildToCancelAsync(graphqlClient, projectId, 'blah')).resolves.toEqual(
selectedBuildId
);
});
});
+3 -3
View File
@@ -63,7 +63,7 @@ This means that it will most likely produce an AAB and you will not be able to i
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
const versionCodeOverride =
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? await resolveRemoteVersionCodeAsync({
? await resolveRemoteVersionCodeAsync(ctx.graphqlClient, {
projectDir: ctx.projectDir,
projectId: ctx.projectId,
exp: ctx.exp,
@@ -84,7 +84,7 @@ export async function prepareAndroidBuildAsync(
return await ensureAndroidCredentialsAsync(ctx);
},
syncProjectConfigurationAsync: async () => {
await syncProjectConfigurationAsync({
await syncProjectConfigurationAsync(ctx.graphqlClient, {
projectDir: ctx.projectDir,
exp: ctx.exp,
localAutoIncrement:
@@ -108,7 +108,7 @@ export async function prepareAndroidBuildAsync(
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
return await BuildMutation.createAndroidBuildAsync({
return await BuildMutation.createAndroidBuildAsync(ctx.graphqlClient, {
appId,
job: graphqlJob,
metadata: graphqlMetadata,
@@ -6,30 +6,34 @@ import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import Log from '../../log';
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow';
import { syncUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
export async function syncProjectConfigurationAsync({
projectDir,
exp,
localAutoIncrement,
projectId,
}: {
projectDir: string;
exp: ExpoConfig;
localAutoIncrement?: AndroidVersionAutoIncrement;
projectId: string;
}): Promise<void> {
export async function syncProjectConfigurationAsync(
graphqlClient: ExpoGraphqlClient,
{
projectDir,
exp,
localAutoIncrement,
projectId,
}: {
projectDir: string;
exp: ExpoConfig;
localAutoIncrement?: AndroidVersionAutoIncrement;
projectId: string;
}
): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync(projectDir, exp, projectId);
await syncUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
} else {
+20 -15
View File
@@ -5,6 +5,7 @@ import { BuildProfile } from '@expo/eas-json';
import chalk from 'chalk';
import fs from 'fs-extra';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { AppPlatform } from '../../graphql/generated';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
@@ -174,20 +175,24 @@ async function writeBuildGradleAsync({
* Returns buildNumber that will be used for the next build. If current build profile
* has an 'autoIncrement' option set, it increments the version on server.
*/
export async function resolveRemoteVersionCodeAsync({
projectDir,
projectId,
exp,
applicationId,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationId: string;
buildProfile: BuildProfile<Platform.ANDROID>;
}): Promise<string> {
export async function resolveRemoteVersionCodeAsync(
graphqlClient: ExpoGraphqlClient,
{
projectDir,
projectId,
exp,
applicationId,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationId: string;
buildProfile: BuildProfile<Platform.ANDROID>;
}
): Promise<string> {
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
AppPlatform.Android,
applicationId
@@ -217,7 +222,7 @@ export async function resolveRemoteVersionCodeAsync({
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
@@ -239,7 +244,7 @@ export async function resolveRemoteVersionCodeAsync({
)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
+9 -3
View File
@@ -8,6 +8,7 @@ import nullthrows from 'nullthrows';
import { withAnalyticsAsync } from '../analytics/common';
import { BuildEvent } from '../analytics/events';
import { getExpoWebsiteBaseUrl } from '../api';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import {
AppPlatform,
BuildFragment,
@@ -213,6 +214,7 @@ async function uploadProjectAsync<TPlatform extends Platform>(
projectTarballPath = projectTarball.path;
const { bucketKey } = await uploadFileAtPathToS3Async(
ctx.graphqlClient,
UploadSessionType.EasBuildProjectSources,
projectTarball.path,
createProgressTracker({
@@ -275,6 +277,7 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
type MaybeBuildFragment = BuildFragment | null;
export async function waitForBuildEndAsync(
graphqlClient: ExpoGraphqlClient,
{ buildIds, accountName }: { buildIds: string[]; accountName: string },
{ intervalSec = 10 } = {}
): Promise<MaybeBuildFragment[]> {
@@ -289,7 +292,7 @@ export async function waitForBuildEndAsync(
spinner = ora('Waiting for builds to complete. You can press Ctrl+C to exit.').start();
}
while (true) {
const builds = await getBuildsSafelyAsync(buildIds);
const builds = await getBuildsSafelyAsync(graphqlClient, buildIds);
const { refetch } =
builds.length === 1
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
@@ -301,10 +304,13 @@ export async function waitForBuildEndAsync(
}
}
async function getBuildsSafelyAsync(buildIds: string[]): Promise<MaybeBuildFragment[]> {
async function getBuildsSafelyAsync(
graphqlClient: ExpoGraphqlClient,
buildIds: string[]
): Promise<MaybeBuildFragment[]> {
const promises = buildIds.map(async buildId => {
try {
return await BuildQuery.byIdAsync(buildId, { useCache: false });
return await BuildQuery.byIdAsync(graphqlClient, buildId, { useCache: false });
} catch (err) {
Log.debug('Failed to fetch the build status', err);
return null;
+2
View File
@@ -3,6 +3,7 @@ import { Platform, Workflow } from '@expo/eas-build-job';
import { BuildProfile, EasJson } from '@expo/eas-json';
import { TrackingContext } from '../analytics/common';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { CredentialsContext } from '../credentials/context';
import { Target } from '../credentials/ios/types';
import { BuildResourceClass } from '../graphql/generated';
@@ -47,6 +48,7 @@ export interface BuildContext<T extends Platform> {
message?: string;
trackingCtx: TrackingContext;
user: Actor;
graphqlClient: ExpoGraphqlClient;
workflow: Workflow;
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
ios: T extends Platform.IOS ? IosBuildContext : undefined;
+6 -1
View File
@@ -8,6 +8,7 @@ import { v4 as uuidv4 } from 'uuid';
import { TrackingContext } from '../analytics/common';
import { Analytics, BuildEvent } from '../analytics/events';
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { CredentialsContext } from '../credentials/context';
import { BuildResourceClass } from '../graphql/generated';
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
@@ -31,6 +32,7 @@ export async function createBuildContextAsync<T extends Platform>({
resourceClass,
message,
actor,
graphqlClient,
getDynamicProjectConfigAsync,
}: {
buildProfileName: string;
@@ -45,12 +47,13 @@ export async function createBuildContextAsync<T extends Platform>({
resourceClass: BuildResourceClass;
message?: string;
actor: Actor;
graphqlClient: ExpoGraphqlClient;
getDynamicProjectConfigAsync: DynamicConfigContextFn;
}): Promise<BuildContext<T>> {
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
const projectName = exp.slug;
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const workflow = await resolveWorkflowAsync(projectDir, platform);
const accountId = account.id;
const runFromCI = getenv.boolish('CI', false);
@@ -60,6 +63,7 @@ export async function createBuildContextAsync<T extends Platform>({
nonInteractive,
projectDir,
user: actor,
graphqlClient,
env: buildProfile.env,
easJsonCliConfig,
});
@@ -99,6 +103,7 @@ export async function createBuildContextAsync<T extends Platform>({
projectName,
trackingCtx,
user: actor,
graphqlClient,
workflow,
message,
runFromCI,
+3 -3
View File
@@ -46,7 +46,7 @@ export async function createIosContextAsync(
const applicationTarget = findApplicationTarget(targets);
const buildNumberOverride =
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
? await resolveRemoteBuildNumberAsync({
? await resolveRemoteBuildNumberAsync(ctx.graphqlClient, {
projectDir: ctx.projectDir,
projectId: ctx.projectId,
exp: ctx.exp,
@@ -72,7 +72,7 @@ export async function prepareIosBuildAsync(
return ensureIosCredentialsAsync(ctx, ctx.ios.targets);
},
syncProjectConfigurationAsync: async () => {
await syncProjectConfigurationAsync({
await syncProjectConfigurationAsync(ctx.graphqlClient, {
projectDir: ctx.projectDir,
exp: ctx.exp,
targets: ctx.ios.targets,
@@ -100,7 +100,7 @@ export async function prepareIosBuildAsync(
): Promise<BuildResult> => {
const graphqlMetadata = transformMetadata(metadata);
const graphqlJob = transformJob(job);
return await BuildMutation.createIosBuildAsync({
return await BuildMutation.createIosBuildAsync(ctx.graphqlClient, {
appId,
job: graphqlJob,
metadata: graphqlMetadata,
@@ -2,31 +2,35 @@ import { ExpoConfig } from '@expo/config';
import { Platform, Workflow } from '@expo/eas-build-job';
import { IosVersionAutoIncrement } from '@expo/eas-json';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { Target } from '../../credentials/ios/types';
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
import { resolveWorkflowAsync } from '../../project/workflow';
import { syncUpdatesConfigurationAsync } from '../../update/ios/UpdatesModule';
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
export async function syncProjectConfigurationAsync({
projectDir,
exp,
targets,
localAutoIncrement,
projectId,
}: {
projectDir: string;
exp: ExpoConfig;
targets: Target[];
localAutoIncrement?: IosVersionAutoIncrement;
projectId: string;
}): Promise<void> {
export async function syncProjectConfigurationAsync(
graphqlClient: ExpoGraphqlClient,
{
projectDir,
exp,
targets,
localAutoIncrement,
projectId,
}: {
projectDir: string;
exp: ExpoConfig;
targets: Target[];
localAutoIncrement?: IosVersionAutoIncrement;
projectId: string;
}
): Promise<void> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
if (workflow === Workflow.GENERIC) {
if (isExpoUpdatesInstalled(projectDir)) {
await syncUpdatesConfigurationAsync(projectDir, exp, projectId);
await syncUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
}
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
} else {
+20 -15
View File
@@ -6,6 +6,7 @@ import chalk from 'chalk';
import path from 'path';
import type { XCBuildConfiguration } from 'xcode';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { Target } from '../../credentials/ios/types';
import { AppPlatform } from '../../graphql/generated';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
@@ -255,20 +256,24 @@ export function evaluateTemplateString(
* Returns buildNumber that will be used for the next build. If current build profile
* has an 'autoIncrement' option set, it increments the version on server.
*/
export async function resolveRemoteBuildNumberAsync({
projectDir,
projectId,
exp,
applicationTarget,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationTarget: Target;
buildProfile: BuildProfile<Platform.IOS>;
}): Promise<string> {
export async function resolveRemoteBuildNumberAsync(
graphqlClient: ExpoGraphqlClient,
{
projectDir,
projectId,
exp,
applicationTarget,
buildProfile,
}: {
projectDir: string;
projectId: string;
exp: ExpoConfig;
applicationTarget: Target;
buildProfile: BuildProfile<Platform.IOS>;
}
): Promise<string> {
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
AppPlatform.Ios,
applicationTarget.bundleIdentifier
@@ -307,7 +312,7 @@ export async function resolveRemoteBuildNumberAsync({
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
appId: projectId,
platform: AppPlatform.Ios,
applicationIdentifier: applicationTarget.bundleIdentifier,
@@ -329,7 +334,7 @@ export async function resolveRemoteBuildNumberAsync({
)}.`
).start();
try {
await AppVersionMutation.createAppVersionAsync({
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
appId: projectId,
platform: AppPlatform.Ios,
applicationIdentifier: applicationTarget.bundleIdentifier,
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.110';
const PLUGIN_PACKAGE_VERSION = '0.0.114';
export interface LocalBuildOptions {
enable: boolean;
+17 -13
View File
@@ -1,5 +1,6 @@
import chalk from 'chalk';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { BuildFilter, BuildFragment } from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
@@ -10,19 +11,22 @@ import { formatGraphQLBuild } from './utils/formatBuild';
export const BUILDS_LIMIT = 50;
export async function listAndRenderBuildsOnAppAsync({
projectId,
projectDisplayName,
filter,
paginatedQueryOptions,
}: {
projectId: string;
projectDisplayName: string;
filter?: BuildFilter;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<void> {
export async function listAndRenderBuildsOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
projectDisplayName,
filter,
paginatedQueryOptions,
}: {
projectId: string;
projectDisplayName: string;
filter?: BuildFilter;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<void> {
if (paginatedQueryOptions.nonInteractive) {
const builds = await BuildQuery.viewBuildsOnAppAsync({
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
offset: paginatedQueryOptions.offset,
@@ -34,7 +38,7 @@ export async function listAndRenderBuildsOnAppAsync({
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) =>
BuildQuery.viewBuildsOnAppAsync({
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
limit,
offset,
@@ -11,6 +11,7 @@ import chalk from 'chalk';
import nullthrows from 'nullthrows';
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import {
AppPlatform,
BuildFragment,
@@ -114,6 +115,7 @@ function resolveResourceClass(
}
export async function runBuildAndSubmitAsync(
graphqlClient: ExpoGraphqlClient,
projectDir: string,
flags: BuildFlags,
actor: Actor,
@@ -166,6 +168,7 @@ export async function runBuildAndSubmitAsync(
),
easJsonCliConfig,
actor,
graphqlClient,
getDynamicProjectConfigAsync,
});
if (maybeBuild) {
@@ -205,7 +208,10 @@ export async function runBuildAndSubmitAsync(
submitProfile,
nonInteractive: flags.nonInteractive,
});
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(startedBuild.build.id);
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(
graphqlClient,
startedBuild.build.id
);
submissions.push(submission);
}
@@ -222,7 +228,7 @@ export async function runBuildAndSubmitAsync(
}
const { accountName } = Object.values(buildCtxByPlatform)[0];
const builds = await waitForBuildEndAsync({
const builds = await waitForBuildEndAsync(graphqlClient, {
buildIds: startedBuilds.map(({ build }) => build.id),
accountName,
});
@@ -239,13 +245,16 @@ export async function runBuildAndSubmitAsync(
}
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
} else {
const completedSubmissions = await waitForSubmissionsToCompleteAsync(submissions);
const completedSubmissions = await waitForSubmissionsToCompleteAsync(
graphqlClient,
submissions
);
if (flags.json) {
printJsonOnlyOutput(
await Promise.all(
builds
.filter((i): i is BuildWithSubmissionsFragment => !!i)
.map(build => BuildQuery.withSubmissionsByIdAsync(build.id))
.map(build => BuildQuery.withSubmissionsByIdAsync(graphqlClient, build.id))
)
);
}
@@ -261,6 +270,7 @@ async function prepareAndStartBuildAsync({
resourceClass,
easJsonCliConfig,
actor,
graphqlClient,
getDynamicProjectConfigAsync,
}: {
projectDir: string;
@@ -270,6 +280,7 @@ async function prepareAndStartBuildAsync({
resourceClass: BuildResourceClass;
easJsonCliConfig: EasJson['cli'];
actor: Actor;
graphqlClient: ExpoGraphqlClient;
getDynamicProjectConfigAsync: DynamicConfigContextFn;
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
const buildCtx = await createBuildContextAsync({
@@ -285,6 +296,7 @@ async function prepareAndStartBuildAsync({
easJsonCliConfig,
message: flags.message,
actor,
graphqlClient,
getDynamicProjectConfigAsync,
});
@@ -365,6 +377,7 @@ async function prepareAndStartSubmissionAsync({
credentialsCtx: buildCtx.credentialsCtx,
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
actor: buildCtx.user,
graphqlClient: buildCtx.graphqlClient,
projectId: buildCtx.projectId,
exp: buildCtx.exp,
});
+49 -38
View File
@@ -1,5 +1,6 @@
import chalk from 'chalk';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import {
ViewBranchesOnUpdateChannelQueryVariables,
@@ -18,15 +19,18 @@ import { logChannelDetails } from './utils';
export const CHANNELS_LIMIT = 25;
export async function selectChannelOnAppAsync({
projectId,
selectionPromptTitle,
paginatedQueryOptions,
}: {
projectId: string;
selectionPromptTitle: string;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<UpdateChannelObject> {
export async function selectChannelOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
selectionPromptTitle,
paginatedQueryOptions,
}: {
projectId: string;
selectionPromptTitle: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<UpdateChannelObject> {
if (paginatedQueryOptions.nonInteractive) {
throw new Error('Unable to select a channel in non-interactive mode.');
}
@@ -34,7 +38,8 @@ export async function selectChannelOnAppAsync({
const updateChannel = await paginatedQueryWithSelectPromptAsync({
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) => queryChannelsOnAppAsync({ appId: projectId, limit, offset }),
queryToPerform: (limit, offset) =>
queryChannelsOnAppAsync(graphqlClient, { appId: projectId, limit, offset }),
promptOptions: {
title: selectionPromptTitle,
createDisplayTextForSelectionPromptListItem: updateChannel => updateChannel.name,
@@ -48,15 +53,18 @@ export async function selectChannelOnAppAsync({
return updateChannel;
}
export async function listAndRenderChannelsOnAppAsync({
projectId,
paginatedQueryOptions,
}: {
projectId: string;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<void> {
export async function listAndRenderChannelsOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId,
paginatedQueryOptions,
}: {
projectId: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<void> {
if (paginatedQueryOptions.nonInteractive) {
const channels = await queryChannelsOnAppAsync({
const channels = await queryChannelsOnAppAsync(graphqlClient, {
appId: projectId,
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
offset: paginatedQueryOptions.offset,
@@ -67,7 +75,7 @@ export async function listAndRenderChannelsOnAppAsync({
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) =>
queryChannelsOnAppAsync({ limit, offset, appId: projectId }),
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
promptOptions: {
title: 'Load more channels?',
renderListItems: channels => renderPageOfChannels(channels, paginatedQueryOptions),
@@ -76,20 +84,23 @@ export async function listAndRenderChannelsOnAppAsync({
}
}
export async function listAndRenderBranchesAndUpdatesOnChannelAsync({
projectId: appId,
channelName,
paginatedQueryOptions,
}: {
projectId: string;
channelName: string;
paginatedQueryOptions: PaginatedQueryOptions;
}): Promise<void> {
const channel = await ChannelQuery.viewUpdateChannelAsync({ appId, channelName });
export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
graphqlClient: ExpoGraphqlClient,
{
projectId: appId,
channelName,
paginatedQueryOptions,
}: {
projectId: string;
channelName: string;
paginatedQueryOptions: PaginatedQueryOptions;
}
): Promise<void> {
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, { appId, channelName });
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
if (paginatedQueryOptions.nonInteractive) {
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync({
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
appId,
channelName,
offset: paginatedQueryOptions.offset,
@@ -101,7 +112,7 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync({
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
offset: paginatedQueryOptions.offset,
queryToPerform: (limit, offset) =>
queryBranchesAndUpdateGroupsOnChannelAsync({
queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
channelName,
appId,
offset,
@@ -116,12 +127,11 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync({
}
}
async function queryChannelsOnAppAsync({
appId,
offset,
limit,
}: ViewUpdateChannelsOnAppQueryVariables): Promise<UpdateChannelObject[]> {
return await ChannelQuery.viewUpdateChannelsOnAppAsync({
async function queryChannelsOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, offset, limit }: ViewUpdateChannelsOnAppQueryVariables
): Promise<UpdateChannelObject[]> {
return await ChannelQuery.viewUpdateChannelsOnAppAsync(graphqlClient, {
appId,
offset,
limit,
@@ -129,9 +139,10 @@ async function queryChannelsOnAppAsync({
}
async function queryBranchesAndUpdateGroupsOnChannelAsync(
graphqlClient: ExpoGraphqlClient,
args: ViewBranchesOnUpdateChannelQueryVariables
): Promise<UpdateBranchOnChannelObject[]> {
return await BranchQuery.listBranchesOnChannelAsync(args);
return await BranchQuery.listBranchesOnChannelAsync(graphqlClient, args);
}
function renderPageOfChannels(
@@ -6,13 +6,15 @@ import {
initAsync as initAnalyticsAsync,
logEvent,
} from '../analytics/rudderstackClient';
import { getUserAsync } from '../user/User';
import ActorContextField from './context/ActorContextField';
import SessionManager from '../user/SessionManager';
import ContextField from './context/ContextField';
import { DynamicProjectConfigContextField } from './context/DynamicProjectConfigContextField';
import LoggedInContextField from './context/LoggedInContextField';
import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
import { OptionalProjectConfigContextField } from './context/OptionalProjectConfigContextField';
import ProjectConfigContextField from './context/ProjectConfigContextField';
import ProjectDirContextField from './context/ProjectDirContextField';
import SessionManagementContextField from './context/SessionManagementContextField';
type ContextInput<
T extends {
@@ -33,10 +35,24 @@ type ContextOutput<
export default abstract class EasCommand extends Command {
protected static readonly ContextOptions = {
/**
* Require this command to be run when logged-in. Returns the logged-in actor in the context.
* Require this command to be run when logged-in. Returns the logged-in actor and a logged-in
* graphql client in the context.
*/
LoggedIn: {
actor: new ActorContextField(),
loggedIn: new LoggedInContextField(),
},
/**
* Do not require this command to be run when logged-in, but if it is get the logged-in actor and a
* maybe-logged-in graphql client.
*/
MaybeLoggedIn: {
maybeLoggedIn: new MaybeLoggedInContextField(),
},
/**
* Specify this context requirement if the command needs to mutate the user session.
*/
SessionManagment: {
sessionManager: new SessionManagementContextField(),
},
/**
* Require the project to be identified and registered on server if this command is being
@@ -92,13 +108,25 @@ export default abstract class EasCommand extends Command {
const contextValuePairs = await Promise.all(
Object.keys(contextDefinition).map(async contextKey => {
return [contextKey, await contextDefinition[contextKey].getValueAsync({ nonInteractive })];
return [
contextKey,
await contextDefinition[contextKey].getValueAsync({
nonInteractive,
sessionManager: this.sessionManager,
}),
];
})
);
return Object.fromEntries(contextValuePairs);
}
/**
* The user session manager. Responsible for coordinating all user session related state.
* If needed in a subclass, SessionManager ContextOption.
*/
private readonly sessionManager = new SessionManager();
protected abstract runAsync(): Promise<any>;
// eslint-disable-next-line async-protect/async-suffix
@@ -106,7 +134,8 @@ export default abstract class EasCommand extends Command {
await initAnalyticsAsync();
// this is needed for logEvent call below as it identifies the user in the analytics system
await getUserAsync();
await this.sessionManager.getUserAsync();
logEvent(AnalyticsEvent.ACTION, {
// id is assigned by oclif in constructor based on the filepath:
// commands/submit === submit, commands/build/list === build:list
@@ -1,9 +1,9 @@
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
import { getUserAsync } from '../../user/User';
import SessionManager from '../../user/SessionManager';
import EasCommand from '../EasCommand';
jest.mock('../../user/User');
jest.mock('../../user/SessionManager');
jest.mock('../../analytics/rudderstackClient', () => {
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
return {
@@ -26,10 +26,6 @@ afterAll(() => {
});
beforeEach(() => {
jest
.mocked(getUserAsync)
.mockReset()
.mockImplementation(async () => mockJester);
jest.mocked(initAsync).mockReset();
jest.mocked(flushAsync).mockReset();
jest.mocked(logEvent).mockReset();
@@ -57,7 +53,8 @@ describe(EasCommand.name, () => {
const TestEasCommand = createTestEasCommand();
await TestEasCommand.run();
expect(jest.mocked(getUserAsync)).toBeCalledTimes(1);
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
expect(sessionManagerSpy).toBeCalledTimes(1);
}, 15_000);
it('initializes analytics', async () => {
@@ -1,9 +0,0 @@
import { Actor } from '../../user/User';
import ContextField, { ContextOptions } from './ContextField';
import { ensureLoggedInAsync } from './contextUtils/ensureLoggedInAsync';
export default class ActorContextField extends ContextField<Actor> {
async getValueAsync({ nonInteractive }: ContextOptions): Promise<Actor> {
return await ensureLoggedInAsync({ nonInteractive });
}
}
@@ -1,4 +1,7 @@
import SessionManager from '../../user/SessionManager';
export interface ContextOptions {
sessionManager: SessionManager;
nonInteractive: boolean;
}
@@ -12,11 +12,14 @@ export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
}>;
export class DynamicProjectConfigContextField extends ContextField<DynamicConfigContextFn> {
async getValueAsync({ nonInteractive }: ContextOptions): Promise<DynamicConfigContextFn> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<DynamicConfigContextFn> {
return async (options?: ExpoConfigOptions) => {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(expBefore, {
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
env: options?.env,
});
@@ -0,0 +1,20 @@
import { Actor } from '../../user/User';
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
export default class LoggedInContextField extends ContextField<{
actor: Actor;
graphqlClient: ExpoGraphqlClient;
}> {
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
actor: Actor;
graphqlClient: ExpoGraphqlClient;
}> {
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
return { actor, graphqlClient };
}
}
@@ -0,0 +1,23 @@
import { Actor } from '../../user/User';
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
export default class MaybeLoggedInContextField extends ContextField<{
actor: Actor | null;
graphqlClient: ExpoGraphqlClient;
}> {
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
actor: Actor | null;
graphqlClient: ExpoGraphqlClient;
}> {
const authenticationInfo = {
accessToken: sessionManager.getAccessToken(),
sessionSecret: sessionManager.getSessionSecret(),
};
const graphqlClient = createGraphqlClient(authenticationInfo);
const actor = await sessionManager.getUserAsync();
return { actor: actor ?? null, graphqlClient };
}
}
@@ -14,7 +14,7 @@ export class OptionalProjectConfigContextField extends ContextField<
}
| undefined
> {
async getValueAsync({ nonInteractive }: ContextOptions): Promise<
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
| {
projectId: string;
exp: ExpoConfig;
@@ -36,7 +36,7 @@ export class OptionalProjectConfigContextField extends ContextField<
}
const expBefore = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(expBefore, {
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
const exp = getExpoConfig(projectDir);
@@ -10,14 +10,14 @@ export default class ProjectConfigContextField extends ContextField<{
exp: ExpoConfig;
projectDir: string;
}> {
async getValueAsync({ nonInteractive }: ContextOptions): Promise<{
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
projectId: string;
exp: ExpoConfig;
projectDir: string;
}> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = getExpoConfig(projectDir);
const projectId = await getProjectIdAsync(expBefore, {
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
const exp = getExpoConfig(projectDir);
@@ -0,0 +1,8 @@
import SessionManager from '../../user/SessionManager';
import ContextField, { ContextOptions } from './ContextField';
export default class SessionManagementContextField extends ContextField<SessionManager> {
async getValueAsync({ sessionManager }: ContextOptions): Promise<SessionManager> {
return sessionManager;
}
}
@@ -1,74 +0,0 @@
import { ApiV2Error } from '../../../../api';
import { promptAsync } from '../../../../prompts';
import { loginAsync } from '../../../../user/User';
import {
UserSecondFactorDeviceMethod,
retryUsernamePasswordAuthWithOTPAsync,
} from '../../../../user/otp';
import { showLoginPromptAsync } from '../../contextUtils/ensureLoggedInAsync';
jest.mock('../../../../prompts');
jest.mock('../../../../api', () => ({
ApiV2Error: jest.requireActual('../../../../api').ApiV2Error,
getExpoApiBaseUrl: jest.fn(),
}));
jest.mock('../../../../user/otp');
jest.mock('../../../../user/User', () => ({
loginAsync: jest.fn(),
}));
beforeEach(() => {
jest.mocked(promptAsync).mockReset();
jest.mocked(promptAsync).mockImplementation(async () => {
throw new Error('Should not be called');
});
jest.mocked(loginAsync).mockReset();
});
describe(showLoginPromptAsync, () => {
it('prompts for OTP when 2FA is enabled', async () => {
jest
.mocked(promptAsync)
.mockImplementationOnce(async () => ({ username: 'hello', password: 'world' }))
.mockImplementationOnce(async () => ({ otp: '123456' }))
.mockImplementation(async () => {
throw new Error("shouldn't happen");
});
jest
.mocked(loginAsync)
.mockImplementationOnce(async () => {
throw new ApiV2Error({
message: 'An OTP is required',
code: 'ONE_TIME_PASSWORD_REQUIRED',
metadata: {
secondFactorDevices: [
{
id: 'p0',
is_primary: true,
method: UserSecondFactorDeviceMethod.SMS,
sms_phone_number: 'testphone',
},
],
smsAutomaticallySent: true,
},
});
})
.mockImplementation(async () => {});
await showLoginPromptAsync();
expect(retryUsernamePasswordAuthWithOTPAsync).toHaveBeenCalledWith('hello', 'world', {
secondFactorDevices: [
{
id: 'p0',
is_primary: true,
method: UserSecondFactorDeviceMethod.SMS,
sms_phone_number: 'testphone',
},
],
smsAutomaticallySent: true,
});
});
});
@@ -1,11 +1,12 @@
import { getConfig, modifyConfigAsync } from '@expo/config';
import { vol } from 'memfs';
import { anything, instance, mock, when } from 'ts-mockito';
import { Role } from '../../../../graphql/generated';
import { AppQuery } from '../../../../graphql/queries/AppQuery';
import { learnMore } from '../../../../log';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { ensureLoggedInAsync } from '../../contextUtils/ensureLoggedInAsync';
import SessionManager from '../../../../user/SessionManager';
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from '../getProjectIdAsync';
@@ -13,7 +14,6 @@ jest.mock('@expo/config');
jest.mock('fs');
jest.mock('../../../../graphql/queries/AppQuery');
jest.mock('../../contextUtils/ensureLoggedInAsync');
jest.mock('../../contextUtils/findProjectDirAndVerifyProjectSetupAsync');
jest.mock('../../../../user/User');
jest.mock('../../../../ora', () => ({
@@ -24,30 +24,37 @@ jest.mock('../../../../ora', () => ({
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
describe(getProjectIdAsync, () => {
let sessionManager: SessionManager;
beforeEach(() => {
jest.mocked(ensureLoggedInAsync).mockResolvedValue({
__typename: 'User',
id: 'user_id',
username: 'notnotbrent',
primaryAccount: {
id: 'account_id_1',
name: 'notnotbrent',
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
},
accounts: [
{
const sessionManagerMock = mock<SessionManager>();
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
actor: {
__typename: 'User',
id: 'user_id',
username: 'notnotbrent',
primaryAccount: {
id: 'account_id_1',
name: 'notnotbrent',
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
},
{
id: 'account_id_2',
name: 'dominik',
users: [{ role: Role.ViewOnly, actor: { id: 'user_id' } }],
},
],
isExpoAdmin: false,
accounts: [
{
id: 'account_id_1',
name: 'notnotbrent',
users: [{ role: Role.Owner, actor: { id: 'user_id' } }],
},
{
id: 'account_id_2',
name: 'dominik',
users: [{ role: Role.ViewOnly, actor: { id: 'user_id' } }],
},
],
isExpoAdmin: false,
},
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
});
sessionManager = instance(sessionManagerMock);
vol.fromJSON(
{
@@ -74,6 +81,7 @@ describe(getProjectIdAsync, () => {
await expect(
getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
{ nonInteractive: false }
)
@@ -93,6 +101,7 @@ describe(getProjectIdAsync, () => {
await expect(
getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
{ nonInteractive: false }
)
@@ -116,6 +125,7 @@ describe(getProjectIdAsync, () => {
await expect(
getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
{ nonInteractive: false }
)
@@ -137,6 +147,7 @@ describe(getProjectIdAsync, () => {
.mockImplementation(async () => '2345');
const projectId = await getProjectIdAsync(
sessionManager,
{ name: 'test', slug: 'test' },
{
nonInteractive: false,
@@ -162,7 +173,7 @@ describe(getProjectIdAsync, () => {
.mockImplementation(async () => '4567');
await expect(
getProjectIdAsync({ name: 'test', slug: 'test' }, { nonInteractive: false })
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
).rejects.toThrow();
});
});
@@ -0,0 +1,63 @@
import {
Client,
OperationContext,
OperationResult,
PromisifiedSource,
TypedDocumentNode,
cacheExchange,
createClient as createUrqlClient,
dedupExchange,
fetchExchange,
} from '@urql/core';
import { retryExchange } from '@urql/exchange-retry';
import { DocumentNode } from 'graphql';
import fetch from 'node-fetch';
import { getExpoApiBaseUrl } from '../../../api';
import { httpsProxyAgent } from '../../../fetch';
export interface ExpoGraphqlClient extends Client {
query<Data = any, Variables extends object = object>(
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
variables: Variables | undefined,
context: Partial<OperationContext> & { additionalTypenames: string[] }
): PromisifiedSource<OperationResult<Data, Variables>>;
}
export function createGraphqlClient(authInfo: {
accessToken: string | null;
sessionSecret: string | null;
}): ExpoGraphqlClient {
return createUrqlClient({
url: getExpoApiBaseUrl() + '/graphql',
exchanges: [
dedupExchange,
cacheExchange,
retryExchange({
maxDelayMs: 4000,
retryIf: (err, operation) => {
return !!(
err &&
!operation.context.noRetry &&
(err.networkError || err.graphQLErrors.some(e => e?.extensions?.isTransient))
);
},
}),
fetchExchange,
],
// @ts-expect-error Type 'typeof fetch' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'.
fetch,
fetchOptions: (): RequestInit => {
const headers: Record<string, string> = {};
if (authInfo.accessToken) {
headers.authorization = `Bearer ${authInfo.accessToken}`;
} else if (authInfo.sessionSecret) {
headers['expo-session'] = authInfo.sessionSecret;
}
return {
...(httpsProxyAgent ? { agent: httpsProxyAgent } : {}),
headers,
};
},
}) as ExpoGraphqlClient;
}
@@ -1,91 +0,0 @@
import { Errors } from '@oclif/core';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
import { ApiV2Error } from '../../../api';
import Log, { learnMore } from '../../../log';
import { promptAsync } from '../../../prompts';
import { Actor, getUserAsync, loginAsync } from '../../../user/User';
import { retryUsernamePasswordAuthWithOTPAsync } from '../../../user/otp';
/**
* Ensure that there is a logged-in actor. Show a login prompt if not.
*
* @param nonInteractive whether the log-in prompt if logged-out should be interactive
* @returns logged-in Actor
*
* @deprecated Should not be used outside of context functions.
*/
export async function ensureLoggedInAsync({
nonInteractive,
}: {
nonInteractive: boolean;
}): Promise<Actor> {
let user: Actor | undefined;
try {
user = await getUserAsync();
} catch {}
if (!user) {
Log.warn('An Expo user account is required to proceed.');
await showLoginPromptAsync({ nonInteractive, printNewLine: true });
user = await getUserAsync();
}
return nullthrows(user);
}
/**
* Prompt the user to log in.
*
* @deprecated Should not be used outside of context functions, except in the AccountLogin command.
*/
export async function showLoginPromptAsync({
nonInteractive = false,
printNewLine = false,
} = {}): Promise<void> {
if (nonInteractive) {
Errors.error(
`Either log in with ${chalk.bold('eas login')} or set the ${chalk.bold(
'EXPO_TOKEN'
)} environment variable if you're using EAS CLI on CI (${learnMore(
'https://docs.expo.dev/accounts/programmatic-access/',
{ dim: false }
)})`
);
}
if (printNewLine) {
Log.newLine();
}
Log.log('Log in to EAS');
const { username, password } = await promptAsync([
{
type: 'text',
name: 'username',
message: 'Email or username',
},
{
type: 'password',
name: 'password',
message: 'Password',
},
]);
try {
await loginAsync({
username,
password,
});
} catch (e) {
if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {
await retryUsernamePasswordAuthWithOTPAsync(
username,
password,
e.expoApiV2ErrorMetadata as any
);
} else {
throw e;
}
}
}
@@ -9,8 +9,9 @@ import { ora } from '../../../ora';
import { getExpoConfig } from '../../../project/expoConfig';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../../project/projectUtils';
import SessionManager from '../../../user/SessionManager';
import { Actor } from '../../../user/User';
import { ensureLoggedInAsync } from './ensureLoggedInAsync';
import { createGraphqlClient } from './createGraphqlClient';
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
/**
@@ -70,13 +71,22 @@ export async function saveProjectIdToAppConfigAsync(
* @deprecated Should not be used outside of context functions.
*/
export async function getProjectIdAsync(
sessionManager: SessionManager,
exp: ExpoConfig,
options: { env?: Env; nonInteractive: boolean }
): Promise<string> {
// all codepaths in this function require a logged-in user with access to the owning account
// since they either query the app via graphql or create it, which includes getting info about
// the owner
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive: options.nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const localProjectId = exp.extra?.eas?.projectId;
if (localProjectId) {
// check that the local project ID matches account and slug
const appForProjectId = await AppQuery.byIdAsync(localProjectId);
const appForProjectId = await AppQuery.byIdAsync(graphqlClient, localProjectId);
if (exp.owner && exp.owner !== appForProjectId.ownerAccount.name) {
throw new Error(
`Project config: Project identified by "extra.eas.projectId" (${
@@ -121,10 +131,8 @@ export async function getProjectIdAsync(
}
};
const actor = await ensureLoggedInAsync({
nonInteractive: options.nonInteractive,
});
const projectId = await fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
graphqlClient,
{
accountName: getAccountNameForEASProjectSync(exp, actor),
projectName: exp.slug,
@@ -1,13 +1,17 @@
import EasCommand from '../../commandUtils/EasCommand';
import { showLoginPromptAsync } from '../../commandUtils/context/contextUtils/ensureLoggedInAsync';
import Log from '../../log';
export default class AccountLogin extends EasCommand {
static override description = 'log in with your Expo account';
static override aliases = ['login'];
static override contextDefinition = {
...this.ContextOptions.SessionManagment,
};
async runAsync(): Promise<void> {
await showLoginPromptAsync();
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
await sessionManager.showLoginPromptAsync();
Log.log('Logged in');
}
}
@@ -1,13 +1,17 @@
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import { logoutAsync } from '../../user/User';
export default class AccountLogout extends EasCommand {
static override description = 'log out';
static override aliases = ['logout'];
static override contextDefinition = {
...this.ContextOptions.SessionManagment,
};
async runAsync(): Promise<void> {
await logoutAsync();
const { sessionManager } = await this.getContextAsync(AccountLogout, { nonInteractive: false });
await sessionManager.logoutAsync();
Log.log('Logged out');
}
}
+10 -4
View File
@@ -2,16 +2,22 @@ import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import Log from '../../log';
import { getActorDisplayName, getUserAsync } from '../../user/User';
import { getActorDisplayName } from '../../user/User';
export default class AccountView extends EasCommand {
static override description = 'show the username you are logged in as';
static override aliases = ['whoami'];
static override contextDefinition = {
...this.ContextOptions.MaybeLoggedIn,
};
async runAsync(): Promise<void> {
const user = await getUserAsync();
if (user) {
Log.log(chalk.green(getActorDisplayName(user)));
const {
maybeLoggedIn: { actor },
} = await this.getContextAsync(AccountView, { nonInteractive: true });
if (actor) {
Log.log(chalk.green(getActorDisplayName(actor)));
} else {
Log.warn('Not logged in');
process.exit(1);
+10 -7
View File
@@ -3,8 +3,9 @@ import gql from 'graphql-tag';
import { getDefaultBranchNameAsync } from '../../branch/utils';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
CreateUpdateBranchForAppMutation,
CreateUpdateBranchForAppMutationVariables,
@@ -15,10 +16,10 @@ import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export async function createUpdateBranchOnAppAsync({
appId,
name,
}: CreateUpdateBranchForAppMutationVariables): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
export async function createUpdateBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name }: CreateUpdateBranchForAppMutationVariables
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const result = await withErrorHandlingAsync(
graphqlClient
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
@@ -63,6 +64,7 @@ export default class BranchCreate extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -72,6 +74,7 @@ export default class BranchCreate extends EasCommand {
} = await this.parse(BranchCreate);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchCreate, {
nonInteractive,
});
@@ -80,7 +83,7 @@ export default class BranchCreate extends EasCommand {
enableJsonOutput();
}
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
if (!name) {
const validationMessage = 'Branch name may not be empty.';
@@ -96,7 +99,7 @@ export default class BranchCreate extends EasCommand {
}));
}
const newBranch = await createUpdateBranchOnAppAsync({ appId: projectId, name });
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, { appId: projectId, name });
if (jsonFlag) {
printJsonOnlyOutput(newBranch);
+16 -12
View File
@@ -3,9 +3,10 @@ import gql from 'graphql-tag';
import { selectBranchOnAppAsync } from '../../branch/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
DeleteUpdateBranchMutation,
DeleteUpdateBranchMutationVariables,
@@ -18,10 +19,10 @@ import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { toggleConfirmAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
async function getBranchInfoAsync({
appId,
name,
}: GetBranchInfoQueryVariables): Promise<GetBranchInfoQuery> {
async function getBranchInfoAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name }: GetBranchInfoQueryVariables
): Promise<GetBranchInfoQuery> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<GetBranchInfoQuery, GetBranchInfoQueryVariables>(
@@ -49,9 +50,10 @@ async function getBranchInfoAsync({
return data;
}
async function deleteBranchOnAppAsync({
branchId,
}: DeleteUpdateBranchMutationVariables): Promise<DeleteUpdateBranchResult> {
async function deleteBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ branchId }: DeleteUpdateBranchMutationVariables
): Promise<DeleteUpdateBranchResult> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<DeleteUpdateBranchMutation, DeleteUpdateBranchMutationVariables>(
@@ -78,6 +80,7 @@ export default class BranchDelete extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
static override args = [
@@ -106,15 +109,16 @@ export default class BranchDelete extends EasCommand {
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchDelete, { nonInteractive });
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
if (!branchName) {
const validationMessage = 'branch name may not be empty.';
if (nonInteractive) {
throw new Error(validationMessage);
}
({ name: branchName } = await selectBranchOnAppAsync({
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
projectId,
displayTextForListItem: updateBranch => updateBranch.name,
promptTitle: 'Which branch would you like to delete?',
@@ -122,7 +126,7 @@ export default class BranchDelete extends EasCommand {
}));
}
const data = await getBranchInfoAsync({ appId: projectId, name: branchName });
const data = await getBranchInfoAsync(graphqlClient, { appId: projectId, name: branchName });
const branchId = data.app?.byId.updateBranchByName?.id;
if (!branchId) {
throw new Error(`Could not find branch ${branchName} on ${projectDisplayName}`);
@@ -142,7 +146,7 @@ export default class BranchDelete extends EasCommand {
}
}
const deletionResult = await deleteBranchOnAppAsync({
const deletionResult = await deleteBranchOnAppAsync(graphqlClient, {
branchId,
});
+3 -1
View File
@@ -14,12 +14,14 @@ export default class BranchList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchList, {
nonInteractive: flags['non-interactive'],
});
@@ -29,6 +31,6 @@ export default class BranchList extends EasCommand {
enableJsonOutput();
}
await listAndRenderBranchesOnAppAsync({ projectId, paginatedQueryOptions });
await listAndRenderBranchesOnAppAsync(graphqlClient, { projectId, paginatedQueryOptions });
}
}
+10 -8
View File
@@ -3,8 +3,9 @@ import chalk from 'chalk';
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
EditUpdateBranchInput,
EditUpdateBranchMutation,
@@ -16,11 +17,10 @@ import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
async function renameUpdateBranchOnAppAsync({
appId,
name,
newName,
}: EditUpdateBranchInput): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
async function renameUpdateBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name, newName }: EditUpdateBranchInput
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<EditUpdateBranchMutation, EditUpdateBranchMutationVariables>(
@@ -64,6 +64,7 @@ export default class BranchRename extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -72,6 +73,7 @@ export default class BranchRename extends EasCommand {
} = await this.parse(BranchRename);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchRename, {
nonInteractive,
});
@@ -79,7 +81,7 @@ export default class BranchRename extends EasCommand {
enableJsonOutput();
}
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
if (!currentName) {
const validationMessage = 'current name may not be empty.';
@@ -107,7 +109,7 @@ export default class BranchRename extends EasCommand {
}));
}
const editedBranch = await renameUpdateBranchOnAppAsync({
const editedBranch = await renameUpdateBranchOnAppAsync(graphqlClient, {
appId: projectId,
name: currentName!,
newName: newName!,
+4 -2
View File
@@ -28,6 +28,7 @@ export default class BranchView extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -38,6 +39,7 @@ export default class BranchView extends EasCommand {
const { 'non-interactive': nonInteractive } = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchView, {
nonInteractive,
});
@@ -52,7 +54,7 @@ export default class BranchView extends EasCommand {
throw new Error('Branch name may not be empty.');
}
({ name: branchName } = await selectBranchOnAppAsync({
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: 'Which branch would you like to view?',
displayTextForListItem: updateBranch => updateBranch.name,
@@ -65,7 +67,7 @@ export default class BranchView extends EasCommand {
}));
}
await listAndRenderUpdateGroupsOnBranchAsync({
await listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
projectId,
branchName,
paginatedQueryOptions,
+21 -11
View File
@@ -2,8 +2,9 @@ import chalk from 'chalk';
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
Build,
BuildStatus,
@@ -17,7 +18,10 @@ import { appPlatformEmojis } from '../../platform';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { confirmAsync, selectAsync } from '../../prompts';
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
async function cancelBuildAsync(
graphqlClient: ExpoGraphqlClient,
buildId: string
): Promise<Pick<Build, 'id' | 'status'>> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<CancelBuildMutation, CancelBuildMutationVariables>(
@@ -56,6 +60,7 @@ function formatUnfinishedBuild(
}
export async function selectBuildToCancelAsync(
graphqlClient: ExpoGraphqlClient,
projectId: string,
projectDisplayName: string
): Promise<string | null> {
@@ -64,19 +69,19 @@ export async function selectBuildToCancelAsync(
let builds;
try {
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
BuildQuery.viewBuildsOnAppAsync({
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
offset: 0,
limit: 10,
filter: { status: BuildStatus.New },
}),
BuildQuery.viewBuildsOnAppAsync({
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
offset: 0,
limit: 10,
filter: { status: BuildStatus.InQueue },
}),
BuildQuery.viewBuildsOnAppAsync({
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
offset: 0,
limit: 10,
@@ -111,9 +116,12 @@ export async function selectBuildToCancelAsync(
}
}
async function ensureBuildExistsAsync(buildId: string): Promise<void> {
async function ensureBuildExistsAsync(
graphqlClient: ExpoGraphqlClient,
buildId: string
): Promise<void> {
try {
await BuildQuery.byIdAsync(buildId);
await BuildQuery.byIdAsync(graphqlClient, buildId);
} catch {
throw new Error(`Couldn't find a build matching the id ${buildId}`);
}
@@ -130,6 +138,7 @@ export default class BuildCancel extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -139,14 +148,15 @@ export default class BuildCancel extends EasCommand {
} = await this.parse(BuildCancel);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildCancel, {
nonInteractive,
});
const displayName = await getDisplayNameForProjectIdAsync(projectId);
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
if (buildIdFromArg) {
await ensureBuildExistsAsync(buildIdFromArg);
await ensureBuildExistsAsync(graphqlClient, buildIdFromArg);
}
let buildId: string | null = buildIdFromArg;
@@ -155,7 +165,7 @@ export default class BuildCancel extends EasCommand {
throw new Error('BUILD_ID must not be empty in non-interactive mode');
}
buildId = await selectBuildToCancelAsync(projectId, displayName);
buildId = await selectBuildToCancelAsync(graphqlClient, projectId, displayName);
if (!buildId) {
return;
}
@@ -163,7 +173,7 @@ export default class BuildCancel extends EasCommand {
const spinner = ora().start('Canceling the build…');
try {
const { status } = await cancelBuildAsync(buildId);
const { status } = await cancelBuildAsync(graphqlClient, buildId);
if (status === BuildStatus.Canceled) {
spinner.succeed('Build canceled');
} else {
@@ -27,12 +27,14 @@ export default class BuildConfigure extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildConfigure);
const {
projectConfig: { exp, projectId, projectDir },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildConfigure, {
nonInteractive: false,
});
@@ -65,14 +67,14 @@ export default class BuildConfigure extends EasCommand {
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
if (workflow === Workflow.GENERIC) {
await syncAndroidUpdatesConfigurationAsync(projectDir, exp, projectId);
await syncAndroidUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
}
}
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
await syncIosUpdatesConfigurationAsync(projectDir, exp, projectId);
await syncIosUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
}
}
}
+7 -1
View File
@@ -110,7 +110,11 @@ export default class Build extends EasCommand {
}
const flags = this.sanitizeFlags(rawFlags);
const { actor, getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(Build, {
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
projectDir,
} = await this.getContextAsync(Build, {
nonInteractive: flags.nonInteractive,
});
@@ -118,6 +122,7 @@ export default class Build extends EasCommand {
if (!flags.localBuildOptions.enable) {
await maybeWarnAboutEasOutagesAsync(
graphqlClient,
flags.autoSubmit
? [StatuspageServiceName.EasBuild, StatuspageServiceName.EasSubmit]
: [StatuspageServiceName.EasBuild]
@@ -127,6 +132,7 @@ export default class Build extends EasCommand {
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
await runBuildAndSubmitAsync(
graphqlClient,
projectDir,
flagsWithPlatform,
actor,
@@ -69,12 +69,13 @@ export default class BuildInspect extends EasCommand {
async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildInspect);
const { actor, getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(
BuildInspect,
{
nonInteractive: false,
}
);
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
projectDir,
} = await this.getContextAsync(BuildInspect, {
nonInteractive: false,
});
const outputDirectory = path.resolve(process.cwd(), flags.output);
const tmpWorkingdir = path.join(getTmpDirectory(), uuidv4());
@@ -95,6 +96,7 @@ export default class BuildInspect extends EasCommand {
} else {
try {
await runBuildAndSubmitAsync(
graphqlClient,
projectDir,
{
nonInteractive: false,
+4 -2
View File
@@ -57,6 +57,7 @@ export default class BuildList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -71,6 +72,7 @@ export default class BuildList extends EasCommand {
} = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, {
nonInteractive,
});
@@ -81,9 +83,9 @@ export default class BuildList extends EasCommand {
const platform = toAppPlatform(requestedPlatform);
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
const displayName = await getDisplayNameForProjectIdAsync(projectId);
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
await listAndRenderBuildsOnAppAsync({
await listAndRenderBuildsOnAppAsync(graphqlClient, {
projectId,
projectDisplayName: displayName,
filter: {
@@ -45,12 +45,13 @@ export default class BuildVersionSetView extends EasCommand {
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionSetView);
const { actor, getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(
BuildVersionSetView,
{
nonInteractive: false,
}
);
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
projectDir,
} = await this.getContextAsync(BuildVersionSetView, {
nonInteractive: false,
});
const platform = await selectPlatformAsync(flags.platform);
const easJsonAccessor = new EasJsonAccessor(projectDir);
@@ -62,7 +63,7 @@ export default class BuildVersionSetView extends EasCommand {
);
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: profile.env });
const displayName = await getDisplayNameForProjectIdAsync(projectId);
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
validateAppConfigForRemoteVersionSource(exp, platform);
@@ -74,6 +75,7 @@ export default class BuildVersionSetView extends EasCommand {
actor
);
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
toAppPlatform(platform),
applicationIdentifier
@@ -104,7 +106,7 @@ export default class BuildVersionSetView extends EasCommand {
? value => isValidVersionCode(value) || `Invalid value: ${VERSION_CODE_REQUIREMENTS}.`
: value => isValidBuildNumber(value) || `Invalid value: ${BUILD_NUMBER_REQUIREMENTS}.`,
});
await AppVersionMutation.createAppVersionAsync({
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
appId: projectId,
platform: toAppPlatform(platform),
applicationIdentifier,
@@ -62,12 +62,13 @@ export default class BuildVersionSyncView extends EasCommand {
public async runAsync(): Promise<void> {
const { flags } = await this.parse(BuildVersionSyncView);
const { actor, getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(
BuildVersionSyncView,
{
nonInteractive: true,
}
);
const {
loggedIn: { actor, graphqlClient },
getDynamicProjectConfigAsync,
projectDir,
} = await this.getContextAsync(BuildVersionSyncView, {
nonInteractive: true,
});
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
const easJsonAccessor = new EasJsonAccessor(projectDir);
@@ -96,6 +97,7 @@ export default class BuildVersionSyncView extends EasCommand {
actor
);
const remoteVersions = await AppVersionQuery.latestVersionAsync(
graphqlClient,
projectId,
toAppPlatform(profileInfo.platform),
applicationIdentifier
+5 -3
View File
@@ -19,6 +19,7 @@ export default class BuildView extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -28,6 +29,7 @@ export default class BuildView extends EasCommand {
} = await this.parse(BuildView);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildView, {
nonInteractive: true,
});
@@ -35,7 +37,7 @@ export default class BuildView extends EasCommand {
enableJsonOutput();
}
const displayName = await getDisplayNameForProjectIdAsync(projectId);
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
const spinner = ora().start('Fetching the build…');
@@ -43,9 +45,9 @@ export default class BuildView extends EasCommand {
let build: BuildFragment;
if (buildId) {
build = await BuildQuery.byIdAsync(buildId);
build = await BuildQuery.byIdAsync(graphqlClient, buildId);
} else {
const builds = await BuildQuery.viewBuildsOnAppAsync({
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId: projectId,
offset: 0,
limit: 1,
+20 -14
View File
@@ -3,8 +3,9 @@ import gql from 'graphql-tag';
import { BranchNotFoundError } from '../../branch/utils';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
CreateUpdateChannelOnAppMutation,
CreateUpdateChannelOnAppMutationVariables,
@@ -17,15 +18,18 @@ import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { createUpdateBranchOnAppAsync } from '../branch/create';
export async function createUpdateChannelOnAppAsync({
appId,
channelName,
branchId,
}: {
appId: string;
channelName: string;
branchId: string;
}): Promise<CreateUpdateChannelOnAppMutation> {
export async function createUpdateChannelOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{
appId,
channelName,
branchId,
}: {
appId: string;
channelName: string;
branchId: string;
}
): Promise<CreateUpdateChannelOnAppMutation> {
// Point the new channel at a branch with its same name.
const branchMapping = JSON.stringify({
data: [{ branchId, branchMappingLogic: 'true' }],
@@ -72,6 +76,7 @@ export default class ChannelCreate extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -81,6 +86,7 @@ export default class ChannelCreate extends EasCommand {
} = await this.parse(ChannelCreate);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelCreate, {
nonInteractive,
});
@@ -105,7 +111,7 @@ export default class ChannelCreate extends EasCommand {
let branchMessage: string;
try {
const branch = await BranchQuery.getBranchByNameAsync({
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: channelName,
});
@@ -113,7 +119,7 @@ export default class ChannelCreate extends EasCommand {
branchMessage = `We found a branch with the same name`;
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newBranch = await createUpdateBranchOnAppAsync({
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId: projectId,
name: channelName,
});
@@ -126,7 +132,7 @@ export default class ChannelCreate extends EasCommand {
const {
updateChannel: { createUpdateChannelForApp: newChannel },
} = await createUpdateChannelOnAppAsync({
} = await createUpdateChannelOnAppAsync(graphqlClient, {
appId: projectId,
channelName,
branchId,
@@ -144,7 +150,7 @@ export default class ChannelCreate extends EasCommand {
Log.addNewLineIfNone();
Log.withTick(
`Created a new channel on project ${chalk.bold(
await getDisplayNameForProjectIdAsync(projectId)
await getDisplayNameForProjectIdAsync(graphqlClient, projectId)
)}`
);
Log.log(
@@ -2,8 +2,9 @@ import gql from 'graphql-tag';
import { selectChannelOnAppAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
DeleteUpdateChannelMutation,
DeleteUpdateChannelMutationVariables,
@@ -31,6 +32,7 @@ export default class ChannelDelete extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -40,6 +42,7 @@ export default class ChannelDelete extends EasCommand {
} = await this.parse(ChannelDelete);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelDelete, {
nonInteractive,
});
@@ -49,7 +52,7 @@ export default class ChannelDelete extends EasCommand {
let channelId, channelName;
if (nameArg) {
const { id, name } = await ChannelQuery.viewUpdateChannelAsync({
const { id, name } = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId: projectId,
channelName: nameArg,
});
@@ -59,7 +62,7 @@ export default class ChannelDelete extends EasCommand {
if (nonInteractive) {
throw new Error('Channel name must be set when running in non-interactive mode');
}
const { id, name } = await selectChannelOnAppAsync({
const { id, name } = await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to delete',
paginatedQueryOptions: {
@@ -85,7 +88,7 @@ export default class ChannelDelete extends EasCommand {
}
}
const deletionResult = await deleteChannelOnAppAsync({
const deletionResult = await deleteChannelOnAppAsync(graphqlClient, {
channelId,
});
@@ -97,9 +100,10 @@ export default class ChannelDelete extends EasCommand {
}
}
async function deleteChannelOnAppAsync({
channelId,
}: DeleteUpdateChannelMutationVariables): Promise<DeleteUpdateChannelResult> {
async function deleteChannelOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ channelId }: DeleteUpdateChannelMutationVariables
): Promise<DeleteUpdateChannelResult> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<DeleteUpdateChannelMutation, DeleteUpdateChannelMutationVariables>(
+16 -12
View File
@@ -5,8 +5,9 @@ import gql from 'graphql-tag';
import { selectBranchOnAppAsync } from '../../branch/queries';
import { selectChannelOnAppAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
UpdateChannelBranchMappingMutation,
UpdateChannelBranchMappingMutationVariables,
@@ -16,12 +17,10 @@ import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export async function updateChannelBranchMappingAsync({
channelId,
branchMapping,
}: UpdateChannelBranchMappingMutationVariables): Promise<
UpdateChannelBranchMappingMutation['updateChannel']['editUpdateChannel']
> {
export async function updateChannelBranchMappingAsync(
graphqlClient: ExpoGraphqlClient,
{ channelId, branchMapping }: UpdateChannelBranchMappingMutationVariables
): Promise<UpdateChannelBranchMappingMutation['updateChannel']['editUpdateChannel']> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<UpdateChannelBranchMappingMutation, UpdateChannelBranchMappingMutationVariables>(
@@ -67,6 +66,7 @@ export default class ChannelEdit extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -76,6 +76,7 @@ export default class ChannelEdit extends EasCommand {
} = await this.parse(ChannelEdit);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelEdit, {
nonInteractive,
});
@@ -84,8 +85,11 @@ export default class ChannelEdit extends EasCommand {
}
const existingChannel = args.name
? await ChannelQuery.viewUpdateChannelAsync({ appId: projectId, channelName: args.name })
: await selectChannelOnAppAsync({
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId: projectId,
channelName: args.name,
})
: await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to edit',
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
@@ -96,11 +100,11 @@ export default class ChannelEdit extends EasCommand {
}
const branch = branchFlag
? await BranchQuery.getBranchByNameAsync({
? await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: branchFlag,
})
: await selectBranchOnAppAsync({
: await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: `Which branch would you like ${existingChannel.name} to point at?`,
displayTextForListItem: updateBranch => updateBranch.name,
@@ -111,7 +115,7 @@ export default class ChannelEdit extends EasCommand {
},
});
const channel = await updateChannelBranchMappingAsync({
const channel = await updateChannelBranchMappingAsync(graphqlClient, {
channelId: existingChannel.id,
// todo: move branch mapping logic to utility
branchMapping: JSON.stringify({
@@ -19,6 +19,7 @@ export default class ChannelList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -27,6 +28,7 @@ export default class ChannelList extends EasCommand {
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelList, {
nonInteractive,
});
@@ -34,7 +36,7 @@ export default class ChannelList extends EasCommand {
enableJsonOutput();
}
await listAndRenderChannelsOnAppAsync({
await listAndRenderChannelsOnAppAsync(graphqlClient, {
projectId,
paginatedQueryOptions,
});
@@ -3,6 +3,7 @@ import chalk from 'chalk';
import { BranchMapping, getBranchMapping } from '../../channel/utils';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { UpdateBranch } from '../../graphql/generated';
import { BranchQuery } from '../../graphql/queries/BranchQuery';
@@ -54,25 +55,28 @@ function getRolloutInfo(channel: UpdateChannelByNameObject): {
return { newBranch, oldBranch, currentPercent };
}
async function startRolloutAsync({
channelName,
branchName,
percent,
projectId,
displayName,
currentBranchMapping,
channel,
nonInteractive,
}: {
channelName?: string;
branchName: string;
percent?: number;
projectId: string;
displayName: string;
currentBranchMapping: BranchMapping;
channel: UpdateChannelByNameObject;
nonInteractive: boolean;
}): Promise<{
async function startRolloutAsync(
graphqlClient: ExpoGraphqlClient,
{
channelName,
branchName,
percent,
projectId,
displayName,
currentBranchMapping,
channel,
nonInteractive,
}: {
channelName?: string;
branchName: string;
percent?: number;
projectId: string;
displayName: string;
currentBranchMapping: BranchMapping;
channel: UpdateChannelByNameObject;
nonInteractive: boolean;
}
): Promise<{
newChannelInfo: {
id: string;
name: string;
@@ -80,7 +84,7 @@ async function startRolloutAsync({
};
logMessage: string;
}> {
const branch = await BranchQuery.getBranchByNameAsync({
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: branchName,
});
@@ -116,7 +120,7 @@ async function startRolloutAsync({
currentBranchMapping.data[0],
],
};
const newChannelInfo = await updateChannelBranchMappingAsync({
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
channelId: channel.id,
branchMapping: JSON.stringify(newBranchMapping),
});
@@ -139,19 +143,22 @@ async function startRolloutAsync({
return { newChannelInfo, logMessage };
}
async function editRolloutAsync({
channelName,
percent,
nonInteractive,
currentBranchMapping,
channel,
}: {
channelName?: string;
percent?: number;
nonInteractive: boolean;
currentBranchMapping: BranchMapping;
channel: UpdateChannelByNameObject;
}): Promise<{
async function editRolloutAsync(
graphqlClient: ExpoGraphqlClient,
{
channelName,
percent,
nonInteractive,
currentBranchMapping,
channel,
}: {
channelName?: string;
percent?: number;
nonInteractive: boolean;
currentBranchMapping: BranchMapping;
channel: UpdateChannelByNameObject;
}
): Promise<{
newChannelInfo: {
id: string;
name: string;
@@ -178,7 +185,7 @@ async function editRolloutAsync({
const newBranchMapping = { ...currentBranchMapping };
newBranchMapping.data[0].branchMappingLogic.operand = percent / 100;
const newChannelInfo = await updateChannelBranchMappingAsync({
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
channelId: channel.id,
branchMapping: JSON.stringify(newBranchMapping),
});
@@ -194,19 +201,22 @@ async function editRolloutAsync({
return { newChannelInfo, logMessage };
}
async function endRolloutAsync({
channelName,
branchName,
nonInteractive,
projectId,
channel,
}: {
channelName?: string;
branchName?: string;
nonInteractive: boolean;
projectId: string;
channel: UpdateChannelByNameObject;
}): Promise<{
async function endRolloutAsync(
graphqlClient: ExpoGraphqlClient,
{
channelName,
branchName,
nonInteractive,
projectId,
channel,
}: {
channelName?: string;
branchName?: string;
nonInteractive: boolean;
projectId: string;
channel: UpdateChannelByNameObject;
}
): Promise<{
newChannelInfo: {
id: string;
name: string;
@@ -219,7 +229,7 @@ async function endRolloutAsync({
let endOnNewBranch;
if (branchName) {
const branch = await BranchQuery.getBranchByNameAsync({
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: branchName,
});
@@ -270,7 +280,7 @@ async function endRolloutAsync({
],
};
const newChannelInfo = await updateChannelBranchMappingAsync({
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
channelId: channel.id,
branchMapping: JSON.stringify(newBranchMapping),
});
@@ -313,6 +323,7 @@ export default class ChannelRollout extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -328,6 +339,7 @@ export default class ChannelRollout extends EasCommand {
} = await this.parse(ChannelRollout);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelRollout, {
nonInteractive,
});
@@ -335,9 +347,9 @@ export default class ChannelRollout extends EasCommand {
enableJsonOutput();
}
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
const channel = await ChannelQuery.viewUpdateChannelAsync({
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId: projectId,
channelName: channelName!,
});
@@ -382,7 +394,7 @@ export default class ChannelRollout extends EasCommand {
logMessage: string;
};
if (!isRollout) {
rolloutMutationResult = await startRolloutAsync({
rolloutMutationResult = await startRolloutAsync(graphqlClient, {
channelName,
branchName: branchName ?? (await promptForBranchNameAsync(channelName, nonInteractive)),
percent,
@@ -393,7 +405,7 @@ export default class ChannelRollout extends EasCommand {
channel,
});
} else if (endFlag) {
rolloutMutationResult = await endRolloutAsync({
rolloutMutationResult = await endRolloutAsync(graphqlClient, {
channelName,
branchName,
nonInteractive,
@@ -401,7 +413,7 @@ export default class ChannelRollout extends EasCommand {
channel,
});
} else {
rolloutMutationResult = await editRolloutAsync({
rolloutMutationResult = await editRolloutAsync(graphqlClient, {
channelName,
percent,
nonInteractive,
@@ -27,6 +27,7 @@ export default class ChannelView extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -38,6 +39,7 @@ export default class ChannelView extends EasCommand {
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelView, {
nonInteractive,
});
@@ -50,7 +52,7 @@ export default class ChannelView extends EasCommand {
if (nonInteractive) {
throw new Error(validationMessage);
}
const selectedUpdateChannel = await selectChannelOnAppAsync({
const selectedUpdateChannel = await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to view',
paginatedQueryOptions: {
@@ -63,7 +65,7 @@ export default class ChannelView extends EasCommand {
assert(channelName, `A channel must be selected.`);
}
await listAndRenderBranchesAndUpdatesOnChannelAsync({
await listAndRenderBranchesAndUpdatesOnChannelAsync(graphqlClient, {
projectId,
channelName,
paginatedQueryOptions,
+10 -2
View File
@@ -17,10 +17,18 @@ export default class Credentials extends EasCommand {
async runAsync(): Promise<void> {
const { flags } = await this.parse(Credentials);
const { actor, projectConfig } = await this.getContextAsync(Credentials, {
const {
loggedIn: { actor, graphqlClient },
projectConfig,
} = await this.getContextAsync(Credentials, {
nonInteractive: false,
});
await new SelectPlatform(actor, projectConfig ?? null, flags.platform).runAsync();
await new SelectPlatform(
actor,
graphqlClient,
projectConfig ?? null,
flags.platform
).runAsync();
}
}
@@ -13,13 +13,17 @@ export default class DeviceCreate extends EasCommand {
async runAsync(): Promise<void> {
// this command is interactive by design
const { actor, projectConfig } = await this.getContextAsync(DeviceCreate, {
const {
loggedIn: { actor, graphqlClient },
projectConfig,
} = await this.getContextAsync(DeviceCreate, {
nonInteractive: false,
});
const ctx = await createContextAsync({
appStore: new AppStoreApi(),
user: actor,
graphqlClient,
projectId: projectConfig?.projectId,
});
const manager = new DeviceManager(ctx);
+10 -6
View File
@@ -3,6 +3,7 @@ import { Flags } from '@oclif/core';
import assert from 'assert';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { PaginatedQueryOptions, getPaginatedQueryOptions } from '../../commandUtils/pagination';
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
@@ -34,6 +35,7 @@ export default class DeviceDelete extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -42,10 +44,11 @@ export default class DeviceDelete extends EasCommand {
let { 'apple-team-id': appleTeamIdentifier, udid } = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceDelete, {
nonInteractive: paginatedQueryOptions.nonInteractive,
});
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
let appleTeamName;
if (paginatedQueryOptions.json) {
@@ -53,7 +56,7 @@ export default class DeviceDelete extends EasCommand {
}
if (!appleTeamIdentifier) {
const appleTeam = await selectAppleTeamOnAccountAsync({
const appleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
accountName: account.name,
selectionPromptTitle: `What Apple team would you like to list devices for?`,
paginatedQueryOptions,
@@ -65,8 +68,8 @@ export default class DeviceDelete extends EasCommand {
assert(appleTeamIdentifier, 'No team identifier is specified');
const chosenDevice = udid
? await AppleDeviceQuery.getByDeviceIdentifierAsync(account.name, udid)
: await selectAppleDeviceOnAppleTeamAsync({
? await AppleDeviceQuery.getByDeviceIdentifierAsync(graphqlClient, account.name, udid)
: await selectAppleDeviceOnAppleTeamAsync(graphqlClient, {
accountName: account.name,
appleTeamIdentifier,
selectionPromptTitle: `Which device would you like to disable?`,
@@ -79,7 +82,7 @@ export default class DeviceDelete extends EasCommand {
return;
}
await this.removeDeviceFromExpoAsync(chosenDevice);
await this.removeDeviceFromExpoAsync(graphqlClient, chosenDevice);
if (await this.shouldDisableDeviceOnAppleAsync(paginatedQueryOptions)) {
await this.disableDeviceOnAppleAsync(chosenDevice, appleTeamIdentifier);
@@ -135,11 +138,12 @@ export default class DeviceDelete extends EasCommand {
}
async removeDeviceFromExpoAsync(
graphqlClient: ExpoGraphqlClient,
chosenDevice: AppleDevice | AppleDeviceQueryResult
): Promise<void> {
const removalSpinner = ora(`Removing Apple device on Expo`).start();
try {
await AppleDeviceMutation.deleteAppleDeviceAsync(chosenDevice.id);
await AppleDeviceMutation.deleteAppleDeviceAsync(graphqlClient, chosenDevice.id);
removalSpinner.succeed('Removed Apple device from Expo');
} catch (err) {
removalSpinner.fail();
+5 -3
View File
@@ -22,6 +22,7 @@ export default class BuildList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -29,6 +30,7 @@ export default class BuildList extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, {
nonInteractive: paginatedQueryOptions.nonInteractive,
});
@@ -38,11 +40,11 @@ export default class BuildList extends EasCommand {
enableJsonOutput();
}
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
// if they don't provide a team id, fetch devices on their account
if (!appleTeamIdentifier) {
const selectedAppleTeam = await selectAppleTeamOnAccountAsync({
const selectedAppleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
accountName: account.name,
paginatedQueryOptions: {
...paginatedQueryOptions,
@@ -56,7 +58,7 @@ export default class BuildList extends EasCommand {
}
assert(appleTeamIdentifier, 'No team identifier is specified');
await listAndRenderAppleDevicesOnAppleTeamAsync({
await listAndRenderAppleDevicesOnAppleTeamAsync(graphqlClient, {
accountName: account.name,
appleTeam: {
appleTeamIdentifier,
+8 -2
View File
@@ -12,6 +12,7 @@ export default class DeviceView extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -32,15 +33,20 @@ If you are not sure what is the UDID of the device you are looking for, run:
}
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceView, {
nonInteractive: true,
});
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const spinner = ora().start(`Fetching device details for ${UDID}…`);
try {
const device = await AppleDeviceQuery.getByDeviceIdentifierAsync(account.name, UDID);
const device = await AppleDeviceQuery.getByDeviceIdentifierAsync(
graphqlClient,
account.name,
UDID
);
if (device) {
spinner.succeed('Fetched device details');
@@ -31,7 +31,7 @@ export default class MetadataPull extends EasCommand {
const { flags } = await this.parse(MetadataPull);
const {
actor,
loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir },
} = await this.getContextAsync(MetadataPull, {
nonInteractive: false,
@@ -44,6 +44,7 @@ export default class MetadataPull extends EasCommand {
projectInfo: { exp, projectId },
projectDir,
user: actor,
graphqlClient,
nonInteractive: false,
});
@@ -29,7 +29,7 @@ export default class MetadataPush extends EasCommand {
const { flags } = await this.parse(MetadataPush);
const {
actor,
loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir },
} = await this.getContextAsync(MetadataPush, {
nonInteractive: false,
@@ -42,6 +42,7 @@ export default class MetadataPush extends EasCommand {
projectInfo: { exp, projectId },
projectDir,
user: actor,
graphqlClient,
nonInteractive: false,
});
+3 -1
View File
@@ -10,17 +10,19 @@ export default class Open extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
// this command is interactive by nature (only really run by humans in a terminal)
const {
projectConfig: { projectId, exp },
loggedIn: { graphqlClient },
} = await this.getContextAsync(Open, {
nonInteractive: false,
});
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
const projectName = exp.slug;
@@ -1,9 +1,11 @@
import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
import chalk from 'chalk';
import { vol } from 'memfs';
import { instance, mock } from 'ts-mockito';
import ActorContextField from '../../../commandUtils/context/ActorContextField';
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
import ProjectDirContextField from '../../../commandUtils/context/ProjectDirContextField';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { saveProjectIdToAppConfigAsync } from '../../../commandUtils/context/contextUtils/getProjectIdAsync';
import { jester } from '../../../credentials/__tests__/fixtures-constants';
import { AppMutation } from '../../../graphql/mutations/AppMutation';
@@ -71,10 +73,13 @@ function mockTestProject(options: {
);
const mockManifest = { exp: appJSON.expo };
const graphqlClient = instance(mock<ExpoGraphqlClient>());
jest.mocked(getConfig).mockReturnValue(mockManifest as any);
jest.spyOn(ProjectDirContextField.prototype, 'getValueAsync').mockResolvedValue('/test-project');
jest.spyOn(ActorContextField.prototype, 'getValueAsync').mockResolvedValue(jester);
jest
.spyOn(LoggedInContextField.prototype, 'getValueAsync')
.mockResolvedValue({ actor: jester, graphqlClient });
}
const commandOptions = { root: '/test-project' } as any;
@@ -1,12 +1,16 @@
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../../graphql/client';
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
import Log from '../../log';
import formatFields from '../../utils/formatFields';
async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
async function projectInfoByIdAsync(
graphqlClient: ExpoGraphqlClient,
appId: string
): Promise<AppInfoQuery> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<AppInfoQuery, AppInfoQueryVariables>(
@@ -34,16 +38,18 @@ export default class ProjectInfo extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ProjectInfo, {
nonInteractive: true,
});
const { app } = await projectInfoByIdAsync(projectId);
const { app } = await projectInfoByIdAsync(graphqlClient, projectId);
if (!app) {
throw new Error(`Could not find project with id: ${projectId}`);
}
@@ -5,6 +5,7 @@ import terminalLink from 'terminal-link';
import { getProjectDashboardUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { saveProjectIdToAppConfigAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
import { AppPrivacy, Role } from '../../graphql/generated';
import { AppMutation } from '../../graphql/mutations/AppMutation';
@@ -100,6 +101,7 @@ export default class ProjectInit extends EasCommand {
}
private static async initializeWithInteractiveSelectionAsync(
graphqlClient: ExpoGraphqlClient,
actor: Actor,
projectDir: string
): Promise<void> {
@@ -159,6 +161,7 @@ export default class ProjectInit extends EasCommand {
const projectName = exp.slug;
const projectFullName = `@${accountName}/${projectName}`;
const existingProjectIdOnServer = await findProjectIdByAccountNameAndSlugNullableAsync(
graphqlClient,
accountName,
projectName
);
@@ -200,7 +203,7 @@ export default class ProjectInit extends EasCommand {
const spinner = ora(`Creating ${chalk.bold(projectFullName)}`).start();
let createdProjectId: string;
try {
createdProjectId = await AppMutation.createAppAsync({
createdProjectId = await AppMutation.createAppAsync(graphqlClient, {
accountId: account.id,
projectName,
privacy: toAppPrivacy(exp.privacy) ?? AppPrivacy.Public,
@@ -218,12 +221,15 @@ export default class ProjectInit extends EasCommand {
const {
flags: { id, force, 'non-interactive': nonInteractive },
} = await this.parse(ProjectInit);
const { actor, projectDir } = await this.getContextAsync(ProjectInit, { nonInteractive });
const {
loggedIn: { actor, graphqlClient },
projectDir,
} = await this.getContextAsync(ProjectInit, { nonInteractive });
if (id) {
await ProjectInit.initializeWithExplicitIDAsync(id, projectDir, { force, nonInteractive });
} else {
await ProjectInit.initializeWithInteractiveSelectionAsync(actor, projectDir);
await ProjectInit.initializeWithInteractiveSelectionAsync(graphqlClient, actor, projectDir);
}
}
}
+10 -4
View File
@@ -51,6 +51,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -66,12 +67,13 @@ export default class EnvironmentSecretCreate extends EasCommand {
} = await this.parse(EnvironmentSecretCreate);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretCreate, {
nonInteractive,
});
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const ownerAccount = await getOwnerAccountForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
const ownerAccount = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
if (!scope) {
const validationMessage = 'Secret scope may not be empty.';
@@ -178,12 +180,13 @@ export default class EnvironmentSecretCreate extends EasCommand {
if (scope === EnvironmentSecretScope.PROJECT) {
if (force) {
const { appSecrets: existingSecrets } = await EnvironmentSecretsQuery.byAppIdAsync(
graphqlClient,
projectId
);
const existingSecret = existingSecrets.find(secret => secret.name === name);
if (existingSecret) {
await EnvironmentSecretMutation.deleteAsync(existingSecret.id);
await EnvironmentSecretMutation.deleteAsync(graphqlClient, existingSecret.id);
Log.withTick(
`Deleting existing secret ${chalk.bold(name)} on project ${chalk.bold(
projectDisplayName
@@ -193,6 +196,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
}
const secret = await EnvironmentSecretMutation.createForAppAsync(
graphqlClient,
{ name, value: secretValue, type: SecretTypeToEnvironmentSecretType[secretType] },
projectId
);
@@ -218,12 +222,13 @@ export default class EnvironmentSecretCreate extends EasCommand {
} else if (scope === EnvironmentSecretScope.ACCOUNT) {
if (force) {
const { accountSecrets: existingSecrets } = await EnvironmentSecretsQuery.byAppIdAsync(
graphqlClient,
projectId
);
const existingSecret = existingSecrets.find(secret => secret.name === name);
if (existingSecret) {
await EnvironmentSecretMutation.deleteAsync(existingSecret.id);
await EnvironmentSecretMutation.deleteAsync(graphqlClient, existingSecret.id);
Log.withTick(
`Deleting existing secret ${chalk.bold(name)} on account ${chalk.bold(
@@ -234,6 +239,7 @@ export default class EnvironmentSecretCreate extends EasCommand {
}
const secret = await EnvironmentSecretMutation.createForAccountAsync(
graphqlClient,
{ name, value: secretValue, type: SecretTypeToEnvironmentSecretType[secretType] },
ownerAccount.id
);
@@ -23,6 +23,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -31,6 +32,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
} = await this.parse(EnvironmentSecretDelete);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretDelete, {
nonInteractive,
});
@@ -42,7 +44,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
throw new Error(validationMessage);
}
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
const secrets = await EnvironmentSecretsQuery.allAsync(graphqlClient, projectId);
({ secret } = await promptAsync({
type: 'autocomplete',
name: 'secret',
@@ -83,7 +85,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
}
}
await EnvironmentSecretMutation.deleteAsync(id);
await EnvironmentSecretMutation.deleteAsync(graphqlClient, id);
Log.withTick(`️Deleted secret${secret?.name ? ` "${secret?.name}"` : ''} with id "${id}".`);
}
+3 -1
View File
@@ -12,16 +12,18 @@ export default class EnvironmentSecretList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentSecretList, {
nonInteractive: true,
});
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
const secrets = await EnvironmentSecretsQuery.allAsync(graphqlClient, projectId);
const table = new Table({
head: ['Name', 'Type', 'Scope', 'ID', 'Updated at'],
+4 -3
View File
@@ -98,7 +98,7 @@ export default class Submit extends EasCommand {
async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(Submit);
const {
actor,
loggedIn: { actor, graphqlClient },
projectConfig: { exp, projectId, projectDir },
} = await this.getContextAsync(Submit, {
nonInteractive: false,
@@ -106,7 +106,7 @@ export default class Submit extends EasCommand {
const flags = this.sanitizeFlags(rawFlags);
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasSubmit]);
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasSubmit]);
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
@@ -128,6 +128,7 @@ export default class Submit extends EasCommand {
archiveFlags: flagsWithPlatform.archiveFlags,
nonInteractive: flagsWithPlatform.nonInteractive,
actor,
graphqlClient,
exp,
projectId,
});
@@ -150,7 +151,7 @@ export default class Submit extends EasCommand {
printSubmissionDetailsUrls(submissions);
if (flagsWithPlatform.wait) {
const completedSubmissions = await waitToCompleteAsync(submissions, {
const completedSubmissions = await waitToCompleteAsync(graphqlClient, submissions, {
verbose: flagsWithPlatform.verbose,
});
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
@@ -34,6 +34,7 @@ export default class UpdateConfigure extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -43,6 +44,7 @@ export default class UpdateConfigure extends EasCommand {
const { flags } = await this.parse(UpdateConfigure);
const {
projectConfig: { projectId, exp, projectDir },
loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateConfigure, {
nonInteractive: true,
});
@@ -74,14 +76,14 @@ export default class UpdateConfigure extends EasCommand {
[RequestedPlatform.Android, RequestedPlatform.All].includes(platform) &&
androidWorkflow === Workflow.GENERIC
) {
await syncAndroidUpdatesConfigurationAsync(projectDir, updatedExp, projectId);
await syncAndroidUpdatesConfigurationAsync(graphqlClient, projectDir, updatedExp, projectId);
Log.withTick(`Configured ${chalk.bold('AndroidManifest.xml')} for EAS Update`);
}
if (
[RequestedPlatform.Ios, RequestedPlatform.All].includes(platform) &&
iosWorkflow === Workflow.GENERIC
) {
await syncIosUpdatesConfigurationAsync(projectDir, updatedExp, projectId);
await syncIosUpdatesConfigurationAsync(graphqlClient, projectDir, updatedExp, projectId);
Log.withTick(`Configured ${chalk.bold('Expo.plist')} for EAS Update`);
}
+19 -7
View File
@@ -2,8 +2,9 @@ import chalk from 'chalk';
import gql from 'graphql-tag';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
DeleteUpdateGroupMutation,
UpdateMutationDeleteUpdateGroupArgs,
@@ -12,11 +13,14 @@ import Log from '../../log';
import { confirmAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
async function deleteUpdateGroupAsync({
group,
}: {
group: string;
}): Promise<DeleteUpdateGroupMutation> {
async function deleteUpdateGroupAsync(
graphqlClient: ExpoGraphqlClient,
{
group,
}: {
group: string;
}
): Promise<DeleteUpdateGroupMutation> {
return await withErrorHandlingAsync(
graphqlClient
.mutation<DeleteUpdateGroupMutation, UpdateMutationDeleteUpdateGroupArgs>(
@@ -50,12 +54,20 @@ export default class UpdateDelete extends EasCommand {
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
args: { groupId: group },
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(UpdateDelete);
const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateDelete, { nonInteractive });
if (jsonFlag) {
enableJsonOutput();
}
@@ -78,7 +90,7 @@ export default class UpdateDelete extends EasCommand {
}
}
await deleteUpdateGroupAsync({ group });
await deleteUpdateGroupAsync(graphqlClient, { group });
if (jsonFlag) {
printJsonOnlyOutput({ group });
+41 -27
View File
@@ -11,6 +11,7 @@ import { selectBranchOnAppAsync } from '../../branch/queries';
import { BranchNotFoundError, getDefaultBranchNameAsync } from '../../branch/utils';
import { getUpdateGroupUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
import fetch from '../../fetch';
@@ -60,17 +61,20 @@ import { createUpdateChannelOnAppAsync } from '../channel/create';
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
export type PublishPlatformFlag = PublishPlatform | 'all';
async function ensureChannelExistsAsync({
appId,
branchId,
channelName,
}: {
appId: string;
branchId: string;
channelName: string;
}): Promise<void> {
async function ensureChannelExistsAsync(
graphqlClient: ExpoGraphqlClient,
{
appId,
branchId,
channelName,
}: {
appId: string;
branchId: string;
channelName: string;
}
): Promise<void> {
try {
await createUpdateChannelOnAppAsync({
await createUpdateChannelOnAppAsync(graphqlClient, {
appId,
channelName,
branchId,
@@ -88,26 +92,29 @@ async function ensureChannelExistsAsync({
}
}
export async function ensureBranchExistsAsync({
appId,
name: branchName,
}: ViewBranchQueryVariables): Promise<{
export async function ensureBranchExistsAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name: branchName }: ViewBranchQueryVariables
): Promise<{
branchId: string;
}> {
try {
const updateBranch = await BranchQuery.getBranchByNameAsync({
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId,
name: branchName,
});
const { id } = updateBranch;
await ensureChannelExistsAsync({ appId, branchId: id, channelName: branchName });
await ensureChannelExistsAsync(graphqlClient, { appId, branchId: id, channelName: branchName });
return { branchId: id };
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newUpdateBranch = await createUpdateBranchOnAppAsync({ appId, name: branchName });
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId,
name: branchName,
});
Log.withTick(`Created branch: ${chalk.bold(branchName)}`);
await ensureChannelExistsAsync({
await ensureChannelExistsAsync(graphqlClient, {
appId,
branchId: newUpdateBranch.id,
channelName: branchName,
@@ -168,6 +175,7 @@ export default class UpdatePublish extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -186,7 +194,10 @@ export default class UpdatePublish extends EasCommand {
'non-interactive': nonInteractive,
json: jsonFlag,
} = flags;
const { getDynamicProjectConfigAsync } = await this.getContextAsync(UpdatePublish, {
const {
getDynamicProjectConfigAsync,
loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdatePublish, {
nonInteractive,
});
@@ -206,7 +217,7 @@ export default class UpdatePublish extends EasCommand {
isPublicConfig: false,
});
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasUpdate]);
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
@@ -246,7 +257,7 @@ export default class UpdatePublish extends EasCommand {
throw new Error('Must supply --branch or use --auto when in non-interactive mode');
} else {
try {
const branch = await selectBranchOnAppAsync({
const branch = await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: `Which branch would you like to ${
republish ? 'republish' : 'publish'
@@ -286,14 +297,16 @@ export default class UpdatePublish extends EasCommand {
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
>[];
if (group) {
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync({ groupId: group });
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
groupId: group,
});
updatesToRepublish = updatesByGroup;
} else {
if (nonInteractive) {
throw new Error('Must supply --group when in non-interactive mode');
}
updatesToRepublish = await selectUpdateGroupOnBranchAsync({
updatesToRepublish = await selectUpdateGroupOnBranchAsync(graphqlClient, {
projectId,
branchName,
paginatedQueryOptions,
@@ -393,6 +406,7 @@ export default class UpdatePublish extends EasCommand {
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
const assets = await collectAssetsAsync({ inputDir: inputDir!, platforms });
const uploadResults = await uploadAssetsAsync(
graphqlClient,
assets,
projectId,
(totalAssets, missingAssets) => {
@@ -423,7 +437,7 @@ export default class UpdatePublish extends EasCommand {
.map(pair => pair[0]);
}
const { branchId } = await ensureBranchExistsAsync({
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
appId: projectId,
name: branchName,
});
@@ -455,7 +469,7 @@ export default class UpdatePublish extends EasCommand {
let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups'];
const publishSpinner = ora('Publishing...').start();
try {
newUpdates = await PublishMutation.publishUpdateGroupAsync(updateGroups);
newUpdates = await PublishMutation.publishUpdateGroupAsync(graphqlClient, updateGroups);
if (codeSigningInfo) {
Log.log('🔒 Signing updates');
@@ -488,7 +502,7 @@ export default class UpdatePublish extends EasCommand {
const manifestSignature = signManifestBody(manifestBody, codeSigningInfo);
await PublishMutation.setCodeSigningInfoAsync(newUpdate.id, {
await PublishMutation.setCodeSigningInfoAsync(graphqlClient, newUpdate.id, {
alg: codeSigningInfo.codeSigningMetadata.alg,
keyid: codeSigningInfo.codeSigningMetadata.keyid,
sig: manifestSignature,
@@ -531,7 +545,7 @@ export default class UpdatePublish extends EasCommand {
const updateGroupId = newUpdatesForRuntimeVersion[0].group;
const projectName = exp.slug;
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
const accountName = (await getOwnerAccountForProjectIdAsync(graphqlClient, projectId)).name;
const updateGroupUrl = getUpdateGroupUrl(accountName, projectName, updateGroupId);
const updateGroupLink = link(updateGroupUrl, { dim: false });
+6 -4
View File
@@ -34,6 +34,7 @@ export default class UpdateList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -41,6 +42,7 @@ export default class UpdateList extends EasCommand {
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateList, {
nonInteractive,
});
@@ -51,10 +53,10 @@ export default class UpdateList extends EasCommand {
}
if (all) {
listAndRenderUpdateGroupsOnAppAsync({ projectId, paginatedQueryOptions });
listAndRenderUpdateGroupsOnAppAsync(graphqlClient, { projectId, paginatedQueryOptions });
} else {
if (branchFlag) {
listAndRenderUpdateGroupsOnBranchAsync({
listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
projectId,
branchName: branchFlag,
paginatedQueryOptions,
@@ -65,7 +67,7 @@ export default class UpdateList extends EasCommand {
throw new Error(validationMessage);
}
const selectedBranch = await selectBranchOnAppAsync({
const selectedBranch = await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: 'Which branch would you like to view?',
displayTextForListItem: updateBranch => updateBranch.name,
@@ -77,7 +79,7 @@ export default class UpdateList extends EasCommand {
offset: 0,
},
});
listAndRenderUpdateGroupsOnBranchAsync({
listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
projectId,
branchName: selectedBranch.name,
paginatedQueryOptions,
+10 -1
View File
@@ -24,16 +24,25 @@ export default class UpdateView extends EasCommand {
...EasJsonOnlyFlag,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
args: { groupId },
flags: { json: jsonFlag },
} = await this.parse(UpdateView);
const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(UpdateView, { nonInteractive: true });
if (jsonFlag) {
enableJsonOutput();
}
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync({ groupId });
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, { groupId });
const [updateGroupDescription] = getUpdateGroupDescriptions([updatesByGroup]);
if (jsonFlag) {
@@ -27,12 +27,14 @@ export default class WebhookCreate extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(WebhookCreate);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookCreate, {
nonInteractive: flags['non-interactive'],
});
@@ -40,7 +42,7 @@ export default class WebhookCreate extends EasCommand {
const spinner = ora('Creating a webhook').start();
try {
await WebhookMutation.createWebhookAsync(projectId, webhookInputParams);
await WebhookMutation.createWebhookAsync(graphqlClient, projectId, webhookInputParams);
spinner.succeed('Successfully created a webhook');
} catch (err) {
spinner.fail('Failed to create a webhook');
@@ -3,6 +3,7 @@ import chalk from 'chalk';
import nullthrows from 'nullthrows';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import { WebhookFragment } from '../../graphql/generated';
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
@@ -29,6 +30,7 @@ export default class WebhookDelete extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -38,14 +40,15 @@ export default class WebhookDelete extends EasCommand {
} = await this.parse(WebhookDelete);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookDelete, {
nonInteractive,
});
let webhook: WebhookFragment | undefined =
webhookId && (await WebhookQuery.byIdAsync(webhookId));
webhookId && (await WebhookQuery.byIdAsync(graphqlClient, webhookId));
if (!webhookId) {
const webhooks = await fetchWebhooksByAppIdAsync(projectId);
const webhooks = await fetchWebhooksByAppIdAsync(graphqlClient, projectId);
if (webhooks.length === 0) {
process.exit(1);
}
@@ -86,7 +89,7 @@ export default class WebhookDelete extends EasCommand {
const spinner = ora('Deleting the webhook').start();
try {
await WebhookMutation.deleteWebhookAsync(webhookId);
await WebhookMutation.deleteWebhookAsync(graphqlClient, webhookId);
spinner.succeed('Successfully deleted the webhook');
} catch (err) {
spinner.fail('Failed to delete the webhook');
@@ -95,10 +98,13 @@ export default class WebhookDelete extends EasCommand {
}
}
async function fetchWebhooksByAppIdAsync(appId: string): Promise<WebhookFragment[]> {
async function fetchWebhooksByAppIdAsync(
graphqlClient: ExpoGraphqlClient,
appId: string
): Promise<WebhookFragment[]> {
const spinner = ora('Fetching webhooks').start();
try {
const webhooks = await WebhookQuery.byAppIdAsync(appId);
const webhooks = await WebhookQuery.byAppIdAsync(graphqlClient, appId);
if (webhooks.length === 0) {
spinner.fail('There are no webhooks on the project');
return [];
@@ -21,6 +21,7 @@ export default class WebhookList extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
@@ -29,15 +30,20 @@ export default class WebhookList extends EasCommand {
} = await this.parse(WebhookList);
const {
projectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookList, {
nonInteractive: true,
});
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
const spinner = ora(`Fetching the list of webhook on project ${projectDisplayName}`).start();
try {
const webhooks = await WebhookQuery.byAppIdAsync(projectId, event && { event });
const webhooks = await WebhookQuery.byAppIdAsync(
graphqlClient,
projectId,
event && { event }
);
if (webhooks.length === 0) {
spinner.fail(`There are no webhooks on project ${projectDisplayName}`);
} else {
@@ -31,12 +31,19 @@ export default class WebhookUpdate extends EasCommand {
...EASNonInteractiveFlag,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(WebhookUpdate);
const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookUpdate, { nonInteractive: flags['non-interactive'] });
const webhookId = flags.id;
const webhook = await WebhookQuery.byIdAsync(webhookId);
const webhook = await WebhookQuery.byIdAsync(graphqlClient, webhookId);
const webhookInputParams = await prepareInputParamsAsync(
pick(flags, ['event', 'url', 'secret', 'non-interactive']),
webhook
@@ -44,7 +51,7 @@ export default class WebhookUpdate extends EasCommand {
const spinner = ora('Updating a webhook').start();
try {
await WebhookMutation.updateWebhookAsync(webhookId, webhookInputParams);
await WebhookMutation.updateWebhookAsync(graphqlClient, webhookId, webhookInputParams);
spinner.succeed('Successfully updated a webhook');
} catch (err) {
spinner.fail('Failed to update a webhook');
@@ -15,14 +15,21 @@ export default class WebhookView extends EasCommand {
},
];
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
args: { ID: webhookId },
} = await this.parse(WebhookView);
const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(WebhookView, { nonInteractive: true });
const spinner = ora(`Fetching the webhook details for ID ${webhookId}`).start();
try {
const webhook = await WebhookQuery.byIdAsync(webhookId);
const webhook = await WebhookQuery.byIdAsync(graphqlClient, webhookId);
spinner.succeed(`Found the webhook details`);
Log.log(`\n${formatWebhook(webhook)}`);
} catch (err) {
@@ -14,8 +14,12 @@ export class AssignFcm {
fcm: AndroidFcmFragment
): Promise<CommonAndroidAppCredentialsFragment> {
const appCredentials =
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(this.app);
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
ctx.graphqlClient,
this.app
);
const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync(
ctx.graphqlClient,
appCredentials,
{
androidFcmId: fcm.id,
@@ -14,8 +14,12 @@ export class AssignGoogleServiceAccountKey {
googleServiceAccountKey: GoogleServiceAccountKeyFragment
): Promise<CommonAndroidAppCredentialsFragment> {
const appCredentials =
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(this.app);
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
ctx.graphqlClient,
this.app
);
const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync(
ctx.graphqlClient,
appCredentials,
{
googleServiceAccountKeyForSubmissionsId: googleServiceAccountKey.id,
@@ -21,13 +21,16 @@ export async function canCopyLegacyCredentialsAsync(
ctx: CredentialsContext,
app: AppLookupParams
): Promise<boolean> {
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(app);
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
ctx.graphqlClient,
app
);
if (appCredentials) {
return false; // modern credentials already exist
}
const legacyAppCredentials =
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(app);
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(ctx.graphqlClient, app);
return !!legacyAppCredentials; // user has some legacy credentials
}
@@ -44,30 +47,37 @@ export async function promptUserAndCopyLegacyCredentialsAsync(
try {
const legacyAppCredentials =
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(app);
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(ctx.graphqlClient, app);
if (!legacyAppCredentials) {
return;
}
const appCredentials =
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(app);
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
ctx.graphqlClient,
app
);
const legacyFcm = legacyAppCredentials.androidFcm;
if (legacyFcm) {
const clonedFcm = await ctx.android.createFcmAsync(
ctx.graphqlClient,
app.account,
legacyFcm.credential,
legacyFcm.version
);
await ctx.android.updateAndroidAppCredentialsAsync(appCredentials, {
await ctx.android.updateAndroidAppCredentialsAsync(ctx.graphqlClient, appCredentials, {
androidFcmId: clonedFcm.id,
});
}
const legacyBuildCredentials = await ctx.android.getLegacyAndroidAppBuildCredentialsAsync(app);
const legacyBuildCredentials = await ctx.android.getLegacyAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
app
);
const legacyKeystore = legacyBuildCredentials?.androidKeystore ?? null;
if (legacyKeystore) {
const clonedKeystore = await ctx.android.createKeystoreAsync(app.account, {
const clonedKeystore = await ctx.android.createKeystoreAsync(ctx.graphqlClient, app.account, {
keystore: legacyKeystore.keystore,
keystorePassword: legacyKeystore.keystorePassword,
keyAlias: legacyKeystore.keyAlias,
@@ -93,7 +103,7 @@ export async function getAppLookupParamsFromContextAsync(
ctx.ensureProjectContext();
const projectName = ctx.exp.slug;
const projectId = ctx.projectId;
const account = await getOwnerAccountForProjectIdAsync(projectId);
const account = await getOwnerAccountForProjectIdAsync(ctx.graphqlClient, projectId);
const androidApplicationIdentifier = await getApplicationIdAsync(
ctx.projectDir,
@@ -125,18 +135,23 @@ export async function createOrUpdateDefaultAndroidAppBuildCredentialsAsync(
'createOrUpdateDefaultAndroidAppBuildCredentialsAsync must be run in interactive mode'
);
const existingDefaultBuildCredentials =
await ctx.android.getDefaultAndroidAppBuildCredentialsAsync(appLookupParams);
await ctx.android.getDefaultAndroidAppBuildCredentialsAsync(ctx.graphqlClient, appLookupParams);
if (existingDefaultBuildCredentials) {
return await ctx.android.updateAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
existingDefaultBuildCredentials,
{ androidKeystoreId }
);
}
return await ctx.android.createAndroidAppBuildCredentialsAsync(appLookupParams, {
name: generateRandomName(),
isDefault: true,
androidKeystoreId,
});
return await ctx.android.createAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
appLookupParams,
{
name: generateRandomName(),
isDefault: true,
androidKeystoreId,
}
);
}
export async function promptForNameAsync(): Promise<string> {
@@ -19,6 +19,7 @@ export class CreateFcm {
},
]);
const fcmFragment = await ctx.android.createFcmAsync(
ctx.graphqlClient,
this.account,
fcmApiKey,
AndroidFcmVersion.Legacy
@@ -20,6 +20,7 @@ export class CreateGoogleServiceAccountKey {
}
const jsonKeyObject = await this.provideAsync(ctx);
const gsaKeyFragment = await ctx.android.createGoogleServiceAccountKeyAsync(
ctx.graphqlClient,
this.account,
jsonKeyObject
);
@@ -1,3 +1,4 @@
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { AccountFragment, AndroidKeystoreFragment } from '../../../graphql/generated';
import Log from '../../../log';
import { CredentialsContext } from '../../context';
@@ -15,19 +16,26 @@ export class CreateKeystore {
}
const projectId = ctx.projectId;
const keystore = await this.provideOrGenerateAsync(projectId);
const keystoreFragment = await ctx.android.createKeystoreAsync(this.account, keystore);
const keystore = await this.provideOrGenerateAsync(ctx.graphqlClient, projectId);
const keystoreFragment = await ctx.android.createKeystoreAsync(
ctx.graphqlClient,
this.account,
keystore
);
Log.succeed('Created keystore');
return keystoreFragment;
}
private async provideOrGenerateAsync(projectId: string): Promise<KeystoreWithType> {
private async provideOrGenerateAsync(
graphqlClient: ExpoGraphqlClient,
projectId: string
): Promise<KeystoreWithType> {
const providedKeystore = await askForUserProvidedAsync(keystoreSchema);
if (providedKeystore) {
const providedKeystoreWithType = getKeystoreWithType(providedKeystore);
validateKeystore(providedKeystoreWithType);
return providedKeystoreWithType;
}
return await generateRandomKeystoreAsync(projectId);
return await generateRandomKeystoreAsync(graphqlClient, projectId);
}
}
@@ -13,6 +13,7 @@ export class RemoveFcm {
);
}
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
ctx.graphqlClient,
this.app
);
const fcm = appCredentials?.androidFcm;
@@ -33,7 +34,7 @@ export class RemoveFcm {
return;
}
await ctx.android.deleteFcmAsync(fcm);
await ctx.android.deleteFcmAsync(ctx.graphqlClient, fcm);
Log.succeed('FCM API Key removed');
}
}
@@ -15,6 +15,7 @@ export class SelectAndRemoveGoogleServiceAccountKey {
}
const gsaKeyFragments = await ctx.android.getGoogleServiceAccountKeysForAccountAsync(
ctx.graphqlClient,
this.account
);
if (gsaKeyFragments.length === 0) {
@@ -47,6 +48,9 @@ export class RemoveGoogleServiceAccountKey {
}
Log.log('Removing Google Service Account Key.');
await ctx.android.deleteGoogleServiceAccountKeyAsync(this.googleServiceAccountKey);
await ctx.android.deleteGoogleServiceAccountKeyAsync(
ctx.graphqlClient,
this.googleServiceAccountKey
);
}
}
@@ -37,7 +37,7 @@ export class RemoveKeystore {
}
await new BackupKeystore(this.app).runAsync(ctx, buildCredentials);
await ctx.android.deleteKeystoreAsync(keystore);
await ctx.android.deleteKeystoreAsync(ctx.graphqlClient, keystore);
Log.succeed('Keystore removed');
}
@@ -68,9 +68,14 @@ export class SetUpBuildCredentials {
keystore: AndroidKeystoreFragment;
}): Promise<AndroidAppBuildCredentialsFragment> {
if (name) {
return await ctx.android.createOrUpdateAndroidAppBuildCredentialsByNameAsync(app, name, {
androidKeystoreId: keystore.id,
});
return await ctx.android.createOrUpdateAndroidAppBuildCredentialsByNameAsync(
ctx.graphqlClient,
app,
name,
{
androidKeystoreId: keystore.id,
}
);
}
return await createOrUpdateDefaultAndroidAppBuildCredentialsAsync(ctx, app, {
androidKeystoreId: keystore.id,
@@ -91,6 +96,7 @@ export class SetUpBuildCredentials {
}
const defaultBuildCredentials = await ctx.android.getDefaultAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
app
);
const defaultKeystore = defaultBuildCredentials?.androidKeystore ?? null;
@@ -113,6 +119,7 @@ export class SetUpBuildCredentials {
name: string;
}): Promise<AndroidAppBuildCredentialsFragment | null> {
const maybeBuildCredentials = await ctx.android.getAndroidAppBuildCredentialsByNameAsync(
ctx.graphqlClient,
app,
name
);
@@ -62,6 +62,7 @@ export class SetUpBuildCredentialsFromCredentialsJson {
}
}
const keystoreFragment = await ctx.android.createKeystoreAsync(
ctx.graphqlClient,
this.app.account,
providedKeystoreWithType
);
@@ -70,12 +71,17 @@ export class SetUpBuildCredentialsFromCredentialsJson {
selectBuildCredentialsResult.resultType ===
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
) {
buildCredentials = await ctx.android.createAndroidAppBuildCredentialsAsync(this.app, {
...selectBuildCredentialsResult.result,
androidKeystoreId: keystoreFragment.id,
});
buildCredentials = await ctx.android.createAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
this.app,
{
...selectBuildCredentialsResult.result,
androidKeystoreId: keystoreFragment.id,
}
);
} else {
buildCredentials = await ctx.android.updateAndroidAppBuildCredentialsAsync(
ctx.graphqlClient,
selectBuildCredentialsResult.result,
{
androidKeystoreId: keystoreFragment.id,
@@ -21,7 +21,10 @@ export class SetUpGoogleServiceAccountKey {
if (isKeySetup) {
Log.succeed('Google Service Account Key already set up.');
return nullthrows(
await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(this.app),
await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
ctx.graphqlClient,
this.app
),
'androidAppCredentials cannot be null if google service account key is already set up'
);
}
@@ -32,6 +35,7 @@ export class SetUpGoogleServiceAccountKey {
}
const keysForAccount = await ctx.android.getGoogleServiceAccountKeysForAccountAsync(
ctx.graphqlClient,
this.app.account
);
let googleServiceAccountKey = null;
@@ -47,6 +51,7 @@ export class SetUpGoogleServiceAccountKey {
private async isGoogleServiceAccountKeySetupAsync(ctx: CredentialsContext): Promise<boolean> {
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
ctx.graphqlClient,
this.app
);
return !!appCredentials?.googleServiceAccountKeyForSubmissions;
@@ -13,6 +13,7 @@ export class UseExistingGoogleServiceAccountKey {
);
}
const gsaKeyFragments = await ctx.android.getGoogleServiceAccountKeysForAccountAsync(
ctx.graphqlClient,
this.account
);
if (gsaKeyFragments.length === 0) {
@@ -40,6 +40,7 @@ describe('CreateKeystore', () => {
// expect keystore to be created on expo servers
expect(ctx.android.createKeystoreAsync).toHaveBeenCalledWith(
ctx.graphqlClient,
appLookupParams.account,
expect.objectContaining(testKeystore)
);
Loaded 100 of 232 files, more files were not shown because too many files have changed in this diff. Show more