mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aadef09d1e |
No files matched your search
@@ -6,35 +6,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Handle more common errors for EAS Submit. ([#1440](https://github.com/expo/eas-cli/pull/1440) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 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
|
||||
|
||||
- Make Apple Platform detection more robust. ([#1417](https://github.com/expo/eas-cli/pull/1417) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Fix creating EAS secret from file. ([#1423](https://github.com/expo/eas-cli/pull/1423) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [2.2.0](https://github.com/expo/eas-cli/releases/tag/v2.2.0) - 2022-10-04
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Prompt before configuring EAS project. ([#1356](https://github.com/expo/eas-cli/pull/1356) by [@wschurman](https://github.com/wschurman))
|
||||
- Adds paginated support to device commands. Removes the ability to delete multiple devices at once. ([#1381](https://github.com/expo/eas-cli/pull/1381) by [@kgc00](https://github.com/kgc00))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
@@ -45,26 +17,14 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Add pagination to channel commands. ([#1352](https://github.com/expo/eas-cli/pull/1352) by [@kgc00](https://github.com/kgc00))
|
||||
- Add pagination to build commands. ([#1353](https://github.com/expo/eas-cli/pull/1353) by [@kgc00](https://github.com/kgc00))
|
||||
- Provide suggestions for developers when archive size is large. ([#1363](https://github.com/expo/eas-cli/pull/1363) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Improve `eas init` command. ([#1376](https://github.com/expo/eas-cli/pull/1376) by [@wschurman](https://github.com/wschurman))
|
||||
- Warn about outdated channel configuration before build when using eas update. ([#1397](https://github.com/expo/eas-cli/pull/1397) by [@kbrandwijk](https://github.com/kbrandwijk))
|
||||
- Validate that owner and projectId and slug all are in alignment. ([#1405](https://github.com/expo/eas-cli/pull/1405) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix automatic eas.json generation when running `eas build`. ([#1415](https://github.com/expo/eas-cli/pull/1415) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Standardize the GQL 'Update' schema using UpdateFragmentNode. ([#1348](https://github.com/expo/eas-cli/pull/1348) by [@kgc00](https://github.com/kgc00))
|
||||
- Remove 'owner' app config field dependency. ([#1368](https://github.com/expo/eas-cli/pull/1368), [#1369](https://github.com/expo/eas-cli/pull/1369), [#1371](https://github.com/expo/eas-cli/pull/1371), [#1372](https://github.com/expo/eas-cli/pull/1372), [#1373](https://github.com/expo/eas-cli/pull/1373), by [@wschurman](https://github.com/wschurman))
|
||||
- Add Xcode 14 image. ([#1365](https://github.com/expo/eas-cli/pull/1365) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Update max `versionCode` for Android. ([#1400](https://github.com/expo/eas-cli/pull/1400) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add and use command context to declare command dependencies. ([#1383](https://github.com/expo/eas-cli/pull/1383), [#1384](https://github.com/expo/eas-cli/pull/1384), [#1387](https://github.com/expo/eas-cli/pull/1387), [#1388](https://github.com/expo/eas-cli/pull/1388), [#1390](https://github.com/expo/eas-cli/pull/1390), [#1391](https://github.com/expo/eas-cli/pull/1391), [#1394](https://github.com/expo/eas-cli/pull/1394), [#1402](https://github.com/expo/eas-cli/pull/1402), [#1403](https://github.com/expo/eas-cli/pull/1403), by [@wschurman](https://github.com/wschurman))
|
||||
- Fix typo in the ad hoc build message. ([#1407](https://github.com/expo/eas-cli/pull/1407) by [@Simek](https://github.com/Simek))
|
||||
- Improve errors and error messages formatting related to **eas.json**. ([#1414](https://github.com/expo/eas-cli/pull/1414) by [@Simek](https://github.com/Simek))
|
||||
- Handle errors from platform-specific kill switches to disable free tier builds. ([#1401](https://github.com/expo/eas-cli/pull/1401) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Surface invalid eas.json errors in an optional project context. ([#1418](https://github.com/expo/eas-cli/pull/1418) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve message when project ID doesn't match either slug or owner. ([#1420](https://github.com/expo/eas-cli/pull/1420) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [2.1.0](https://github.com/expo/eas-cli/releases/tag/v2.1.0) - 2022-09-05
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Releasing a new version
|
||||
|
||||
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`.
|
||||
1. Run `yarn release` in the repository root folder.
|
||||
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
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "2.4.0"
|
||||
"version": "2.1.0"
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
"watch": "yarn start",
|
||||
"eas": "packages/eas-cli/bin/run",
|
||||
"lint": "eslint .",
|
||||
"release": "./scripts/bin/run release",
|
||||
"release": "./scripts/bin/run update-local-plugin && lerna version --exact",
|
||||
"test": "jest",
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
},
|
||||
|
||||
+133
-179
@@ -125,7 +125,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.1.0/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.4.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v2.1.0/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.4.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/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.4.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -210,20 +210,19 @@ create a branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:create [NAME] [--json --non-interactive]
|
||||
$ eas branch:create [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to create
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--json return a json with the new branch ID and name.
|
||||
|
||||
DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -231,20 +230,19 @@ delete a branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:delete [NAME] [--json --non-interactive]
|
||||
$ eas branch:delete [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to delete
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--json return JSON with the edited branch's ID and name.
|
||||
|
||||
DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -252,11 +250,12 @@ list all branches
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:list [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas branch:list [--offset <value>] [--limit <value>] [--json] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--limit=<value> The number of query items to list at once. The default value is 50 (the maximum is 100). Using a
|
||||
lower value may help increase command speed.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
|
||||
@@ -264,7 +263,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -272,19 +271,18 @@ rename a branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:rename [--from <value>] [--to <value>] [--json --non-interactive]
|
||||
$ eas branch:rename [--from <value>] [--to <value>] [--json]
|
||||
|
||||
FLAGS
|
||||
--from=<value> current name of the branch.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--to=<value> new name of the branch.
|
||||
--from=<value> current name of the branch.
|
||||
--json return a json with the edited branch's ID and name.
|
||||
--to=<value> new name of the branch.
|
||||
|
||||
DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -292,14 +290,15 @@ view a branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:view [NAME] [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas branch:view [NAME] [--offset <value>] [--limit <value>] [--json] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to view
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 25 and is capped at 50.
|
||||
--limit=<value> The number of query items to list at once. The default value is 50 (the maximum is 100). Using a
|
||||
lower value may help increase command speed.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
|
||||
@@ -307,7 +306,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -315,29 +314,29 @@ start a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
|
||||
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--json --non-interactive]
|
||||
$ eas build [-p android|ios|all] [--json] [--profile <value>] [--non-interactive] [--local] [--output
|
||||
<value>] [--wait] [--clear-cache] [--auto-submit | --auto-submit-with-profile <value>] [-m <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined
|
||||
in eas.json.
|
||||
-m, --message=<value> A short message describing the build
|
||||
-p, --platform=(android|ios|all)
|
||||
--auto-submit Submit on build complete using the submit profile with the same name as the
|
||||
build profile
|
||||
--auto-submit-with-profile=PROFILE_NAME Submit on build complete using the submit profile with provided name
|
||||
--clear-cache Clear cache before the build
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
--local Run build locally [experimental]
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--output=<value> Output path for local build
|
||||
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined
|
||||
in eas.json.
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
|
||||
DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -345,16 +344,13 @@ cancel a build
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:cancel [BUILD_ID] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
|
||||
DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -371,7 +367,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -379,12 +375,9 @@ inspect the state of the project at specific build stages, useful for troublesho
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:inspect -p android|ios -s archive|pre-build|post-build -o <value> [-e <value>] [--force] [-v]
|
||||
$ eas build:inspect -p android|ios -s archive|pre-build|post-build -o <value> [--profile <value>] [--force] [-v]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME
|
||||
Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
-o, --output=OUTPUT_DIRECTORY
|
||||
(required) Output directory.
|
||||
|
||||
@@ -402,11 +395,14 @@ FLAGS
|
||||
--force
|
||||
Delete OUTPUT_DIRECTORY if it already exists.
|
||||
|
||||
--profile=PROFILE_NAME
|
||||
Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
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.4.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -414,10 +410,10 @@ list all builds for your project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:list [--platform all|android|ios] [--status new|in-queue|in-progress|errored|finished|canceled]
|
||||
[--distribution store|internal|simulator] [--channel <value>] [--appVersion <value>] [--appBuildVersion <value>]
|
||||
[--sdkVersion <value>] [--runtimeVersion <value>] [--appIdentifier <value>] [--buildProfile <value>]
|
||||
[--gitCommitHash <value>] [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas build:list [--platform all|android|ios] [--json] [--status
|
||||
new|in-queue|in-progress|errored|finished|canceled] [--distribution store|internal|simulator] [--channel <value>]
|
||||
[--appVersion <value>] [--appBuildVersion <value>] [--sdkVersion <value>] [--runtimeVersion <value>]
|
||||
[--appIdentifier <value>] [--buildProfile <value>] [--gitCommitHash <value>] [--limit <value>]
|
||||
|
||||
FLAGS
|
||||
--appBuildVersion=<value>
|
||||
@@ -428,12 +424,8 @@ FLAGS
|
||||
--distribution=(store|internal|simulator)
|
||||
--gitCommitHash=<value>
|
||||
--json Enable JSON output, non-JSON messages will be printed
|
||||
to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 10
|
||||
and is capped at 50.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating
|
||||
results. Defaults to 0.
|
||||
to stderr
|
||||
--limit=<value>
|
||||
--platform=(all|android|ios)
|
||||
--runtimeVersion=<value>
|
||||
--sdkVersion=<value>
|
||||
@@ -443,7 +435,7 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -451,17 +443,17 @@ submit app binary to App Store and/or Play Store
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:submit [-p android|ios|all] [-e <value>] [--latest | --id <value> | --path <value> | --url <value>]
|
||||
[--verbose] [--wait] [--non-interactive]
|
||||
$ eas build:submit [-p android|ios|all] [--profile <value>] [--latest | --id <value> | --path <value> | --url
|
||||
<value>] [--verbose] [--wait] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-p, --platform=(android|ios|all)
|
||||
--id=<value> ID of the build to submit
|
||||
--latest Submit the latest build for specified platform
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=<value> Path to the .apk/.aab/.ipa file
|
||||
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
--url=<value> App archive url
|
||||
--verbose Always print logs from Submission Service
|
||||
--[no-]wait Wait for submission to complete
|
||||
@@ -479,18 +471,18 @@ Update version of an app.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:version:set [-p android|ios] [-e <value>]
|
||||
$ eas build:version:set [-p android|ios] [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-p, --platform=(android|ios)
|
||||
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
|
||||
DESCRIPTION
|
||||
Update version of an app.
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v2.4.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.1.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -498,18 +490,18 @@ Update a version in native code with a value stored on EAS servers
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:version:sync [-p android|ios|all] [-e <value>]
|
||||
$ eas build:version:sync [-p android|ios|all] [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-p, --platform=(android|ios|all)
|
||||
--profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
|
||||
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.4.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.1.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -520,13 +512,13 @@ USAGE
|
||||
$ eas build:view [BUILD_ID] [--json]
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
|
||||
DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -534,20 +526,19 @@ create a channel
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:create [NAME] [--json --non-interactive]
|
||||
$ eas channel:create [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to create
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--json print output as a JSON object with the new channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -555,21 +546,20 @@ point a channel at a new branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:edit [NAME] [--branch <value>] [--json --non-interactive]
|
||||
$ eas channel:edit [NAME] [--branch <value>] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to edit
|
||||
|
||||
FLAGS
|
||||
--branch=<value> Name of the branch to point to
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--branch=<value> Name of the branch to point to
|
||||
--json Print output as a JSON object with the channel ID, name and branch mapping
|
||||
|
||||
DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -577,19 +567,16 @@ list all channels
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:list [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas channel:list [--json]
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 10 and is capped at 25.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -597,22 +584,19 @@ view a channel
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:view [NAME] [--json --non-interactive] [--offset <value>] [--limit <value>]
|
||||
$ eas channel:view [NAME] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to view
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
|
||||
DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -620,18 +604,17 @@ display project configuration (app.json + eas.json)
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas config [-p android|ios] [-e <value>]
|
||||
$ eas config [-p android|ios] [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-p, --platform=(android|ios)
|
||||
--profile=<value>
|
||||
|
||||
DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -648,7 +631,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -662,7 +645,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.4.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -670,19 +653,17 @@ remove a registered device from your account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:delete [--apple-team-id <value>] [--udid <value>] [--json --non-interactive]
|
||||
$ eas device:delete [--apple-team-id <value>] [--udid <value>]
|
||||
|
||||
FLAGS
|
||||
--apple-team-id=<value> The Apple team ID on which to find the device
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--udid=<value> The Apple device ID to disable
|
||||
--apple-team-id=<value>
|
||||
--udid=<value>...
|
||||
|
||||
DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -690,20 +671,16 @@ list all registered devices for your account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas device:list [--apple-team-id <value>] [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas device:list [--apple-team-id <value>]
|
||||
|
||||
FLAGS
|
||||
--apple-team-id=<value>
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
|
||||
DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -717,7 +694,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -731,7 +708,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -759,12 +736,7 @@ create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas init [--force --id <value>] [--non-interactive ]
|
||||
|
||||
FLAGS
|
||||
--force Whether to overwrite any existing project ID
|
||||
--id=<value> ID of the EAS project to link
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
$ eas init
|
||||
|
||||
DESCRIPTION
|
||||
create or link an EAS project
|
||||
@@ -809,16 +781,16 @@ generate the local store configuration from the app stores
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas metadata:pull [-e <value>]
|
||||
$ eas metadata:pull [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
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.4.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -826,16 +798,16 @@ sync the local store configuration to the app stores
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas metadata:push [-e <value>]
|
||||
$ eas metadata:push [--profile <value>]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
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.4.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas open`
|
||||
|
||||
@@ -849,7 +821,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -863,7 +835,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -871,12 +843,7 @@ create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:init [--force --id <value>] [--non-interactive ]
|
||||
|
||||
FLAGS
|
||||
--force Whether to overwrite any existing project ID
|
||||
--id=<value> ID of the EAS project to link
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
$ eas project:init
|
||||
|
||||
DESCRIPTION
|
||||
create or link an EAS project
|
||||
@@ -885,7 +852,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -893,22 +860,19 @@ create an environment secret on the current project or owner account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:create [--scope account|project] [--name <value>] [--value <value>] [--type string|file] [--force]
|
||||
[--non-interactive]
|
||||
$ eas secret:create [--scope account|project] [--name <value>] [--value <value>] [--force]
|
||||
|
||||
FLAGS
|
||||
--force Delete and recreate existing secrets
|
||||
--name=<value> Name of the secret
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(account|project) [default: project] Scope for the secret
|
||||
--type=(string|file) The type of secret
|
||||
--value=<value> Text value or path to a file to store in the secret
|
||||
--value=<value> Value of the secret
|
||||
|
||||
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.4.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -916,17 +880,16 @@ delete an environment secret by ID
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:delete [--id <value>] [--non-interactive]
|
||||
$ eas secret:delete [--id <value>]
|
||||
|
||||
FLAGS
|
||||
--id=<value> ID of the secret to delete
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--id=<value> ID of the secret to delete
|
||||
|
||||
DESCRIPTION
|
||||
delete an environment secret by ID
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -940,7 +903,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.4.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -948,17 +911,17 @@ submit app binary to App Store and/or Play Store
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas submit [-p android|ios|all] [-e <value>] [--latest | --id <value> | --path <value> | --url <value>]
|
||||
[--verbose] [--wait] [--non-interactive]
|
||||
$ eas submit [-p android|ios|all] [--profile <value>] [--latest | --id <value> | --path <value> | --url
|
||||
<value>] [--verbose] [--wait] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
-p, --platform=(android|ios|all)
|
||||
--id=<value> ID of the build to submit
|
||||
--latest Submit the latest build for specified platform
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=<value> Path to the .apk/.aab/.ipa file
|
||||
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
|
||||
eas.json.
|
||||
--url=<value> App archive url
|
||||
--verbose Always print logs from Submission Service
|
||||
--[no-]wait Wait for submission to complete
|
||||
@@ -970,7 +933,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -979,7 +942,7 @@ publish an update group
|
||||
```
|
||||
USAGE
|
||||
$ eas update [--branch <value>] [--message <value>] [--republish | --input-dir <value> | --skip-bundler]
|
||||
[--group <value> | | ] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json --non-interactive]
|
||||
[--group <value> | | ] [-p android|ios|all] [--json] [--auto] [--private-key-path <value>] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
@@ -987,9 +950,9 @@ FLAGS
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--group=<value> Update group to republish
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
--message=<value> A short message describing the update
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory.
|
||||
@@ -1000,7 +963,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1017,7 +980,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1025,42 +988,38 @@ delete all the updates in an update group
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:delete [GROUPID] [--json --non-interactive]
|
||||
$ eas update:delete [GROUPID] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
GROUPID The ID of an update group to delete.
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--json Return a json with the group ID of the deleted updates.
|
||||
|
||||
DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
view the recent updates
|
||||
view the recent updates for a branch
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:list [--branch <value> | --all] [--offset <value>] [--limit <value>] [--json --non-interactive]
|
||||
$ eas update:list [--branch <value> | --all] [--json]
|
||||
|
||||
FLAGS
|
||||
--all List updates on all branches
|
||||
--branch=<value> List updates only on this branch
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 25 and is capped at 50.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--all List all updates associated with this project
|
||||
--branch=<value> List all updates on this branch
|
||||
--json Return a json with all of the recent update groups.
|
||||
|
||||
DESCRIPTION
|
||||
view the recent updates
|
||||
view the recent updates for a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1074,13 +1033,13 @@ ARGUMENTS
|
||||
GROUPID The ID of an update group.
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--json Return a json with the updates belonging to the group.
|
||||
|
||||
DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1088,11 +1047,10 @@ create a webhook
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:create [--event BUILD|SUBMIT] [--url <value>] [--secret <value>] [--non-interactive]
|
||||
$ eas webhook:create [--event BUILD|SUBMIT] [--url <value>] [--secret <value>]
|
||||
|
||||
FLAGS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--secret=<value> Secret used to create a hash signature of the request payload, provided in the
|
||||
'Expo-Signature' header.
|
||||
--url=<value> Webhook URL
|
||||
@@ -1101,7 +1059,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1109,19 +1067,16 @@ delete a webhook
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:delete [ID] [--non-interactive]
|
||||
$ eas webhook:delete [ID]
|
||||
|
||||
ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1138,7 +1093,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1146,12 +1101,11 @@ update a webhook
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas webhook:update --id <value> [--event BUILD|SUBMIT] [--url <value>] [--secret <value>] [--non-interactive]
|
||||
$ eas webhook:update --id <value> [--event BUILD|SUBMIT] [--url <value>] [--secret <value>]
|
||||
|
||||
FLAGS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
--id=<value> (required) Webhook ID
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--secret=<value> Secret used to create a hash signature of the request payload, provided in the
|
||||
'Expo-Signature' header.
|
||||
--url=<value> Webhook URL
|
||||
@@ -1160,7 +1114,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1177,7 +1131,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.4.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.1.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
|
||||
Generated
+66
-525
@@ -474,30 +474,6 @@
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
@@ -620,30 +596,6 @@
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
@@ -2037,6 +1989,71 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "subscribeToProduct",
|
||||
"description": "Add a subscription",
|
||||
"args": [
|
||||
{
|
||||
"name": "accountName",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "paymentSource",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "productId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Account",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@@ -6741,18 +6758,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "githubRepositorySettings",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettings",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "githubUrl",
|
||||
"description": "githubUrl field from most recent classic update manifest",
|
||||
@@ -11135,32 +11140,7 @@
|
||||
{
|
||||
"name": "appleDevices",
|
||||
"description": "",
|
||||
"args": [
|
||||
{
|
||||
"name": "limit",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
@@ -14208,50 +14188,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildOrBuildJobQuery",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "byId",
|
||||
"description": "Look up EAS Build or Classic Build Job by ID",
|
||||
"args": [
|
||||
{
|
||||
"name": "buildOrBuildJobId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "UNION",
|
||||
"name": "EASBuildOrClassicBuildJob",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "BuildParamsInput",
|
||||
@@ -14693,12 +14629,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "IOS_M1_LARGE",
|
||||
"description": "@experimental This resource class is not yet ready to be used in production. For testing purposes only.",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "LEGACY",
|
||||
"description": "",
|
||||
@@ -15647,49 +15577,6 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateGitHubRepositorySettingsInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "appId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "baseDirectory",
|
||||
"description": "The base directory is the directory to change to before starting a build. This string should be a properly formatted Unix path starting with '/', './', or the name of the directory relative to the root of the repository. Valid examples include: '/apps/expo-app', './apps/expo-app', and 'apps/expo-app'. This is intended for monorepos or apps that live in a subdirectory of a repository.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateIosSubmissionInput",
|
||||
@@ -16471,27 +16358,6 @@
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "UNION",
|
||||
"name": "EASBuildOrClassicBuildJob",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Build",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildJob",
|
||||
"ofType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "EASServiceMetric",
|
||||
@@ -17715,87 +17581,6 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "searchRepositories",
|
||||
"description": "",
|
||||
"args": [
|
||||
{
|
||||
"name": "githubAppInstallationId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "1",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "perPage",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "50",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubAppInstallationAccessibleRepository",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@@ -18161,191 +17946,6 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettings",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "app",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "App",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "baseDirectory",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettingsMutation",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "createGitHubRepositorySettings",
|
||||
"description": "Create GitHub repository settings for an App",
|
||||
"args": [
|
||||
{
|
||||
"name": "githubRepositorySettingsData",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateGitHubRepositorySettingsInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettings",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "deleteGitHubRepositorySettings",
|
||||
"description": "Delete GitHub repository settings by ID",
|
||||
"args": [
|
||||
{
|
||||
"name": "githubRepositorySettingsId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettings",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updateGitHubRepositorySettings",
|
||||
"description": "Update GitHub repository settings",
|
||||
"args": [
|
||||
{
|
||||
"name": "githubRepositorySettingsData",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateGitHubRepositorySettingsInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "githubRepositorySettingsId",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettings",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GoogleServiceAccountKey",
|
||||
@@ -23789,22 +23389,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "githubRepositorySettings",
|
||||
"description": "Mutations for GitHub repository settings",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GitHubRepositorySettingsMutation",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "googleServiceAccountKey",
|
||||
"description": "Mutations that modify a Google Service Account Key",
|
||||
@@ -24282,22 +23866,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "buildOrBuildJob",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildOrBuildJobQuery",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "buildPublicData",
|
||||
"description": "Top-level query object for querying BuildPublicData publicly.",
|
||||
@@ -27950,33 +27518,6 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateGitHubRepositorySettingsInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "baseDirectory",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateInfoGroup",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "2.4.0",
|
||||
"version": "2.1.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -14,7 +14,7 @@
|
||||
"@expo/config-plugins": "5.0.1",
|
||||
"@expo/config-types": "46.0.2",
|
||||
"@expo/eas-build-job": "0.2.94",
|
||||
"@expo/eas-json": "2.2.0",
|
||||
"@expo/eas-json": "2.1.0",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/package-manager": "0.0.56",
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import assert from 'assert';
|
||||
import nock from 'nock';
|
||||
|
||||
import { ApiV2Error } from '../ApiV2Error';
|
||||
import { ApiV2Client, getExpoApiBaseUrl } from '../api';
|
||||
import { ApiV2Error, api, getExpoApiBaseUrl } from '../api';
|
||||
|
||||
describe(ApiV2Client, () => {
|
||||
describe('apiClient', () => {
|
||||
it('converts Expo APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl())
|
||||
.post('/v2/test')
|
||||
@@ -22,9 +21,7 @@ describe(ApiV2Client, () => {
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await new ApiV2Client({ accessToken: null, sessionSecret: null }).postAsync('test', {
|
||||
body: {},
|
||||
});
|
||||
await api.postAsync('test', { body: {} });
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
@@ -44,9 +41,7 @@ describe(ApiV2Client, () => {
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await new ApiV2Client({ accessToken: null, sessionSecret: null }).postAsync('test', {
|
||||
body: {},
|
||||
});
|
||||
await api.postAsync('test', { body: {} });
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
+74
-59
@@ -1,78 +1,93 @@
|
||||
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 class ApiV2Client {
|
||||
constructor(
|
||||
private readonly authInfo: {
|
||||
accessToken: string | null;
|
||||
sessionSecret: string | null;
|
||||
}
|
||||
) {}
|
||||
export const api = {
|
||||
async putAsync(path: string, options: RequestOptions): Promise<any> {
|
||||
return await requestAsync(path, { method: 'PUT', body: JSON.stringify(options.body) });
|
||||
},
|
||||
|
||||
public async putAsync(path: string, options: RequestOptions): Promise<any> {
|
||||
return await this.requestAsync(path, { method: 'PUT', body: JSON.stringify(options.body) });
|
||||
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 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> {
|
||||
async function handleApiErrorAsync(err: any): Promise<void> {
|
||||
if (err instanceof RequestError) {
|
||||
let result: { [key: string]: any };
|
||||
try {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
function getAuthHeaders(): Record<string, string> {
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
return { authorization: `Bearer ${token}` };
|
||||
}
|
||||
const sessionSecret = getSessionSecret();
|
||||
if (sessionSecret) {
|
||||
return { 'expo-session': sessionSecret };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function getExpoApiBaseUrl(): string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -15,20 +14,17 @@ import {
|
||||
|
||||
export const BRANCHES_LIMIT = 50;
|
||||
|
||||
export async function selectBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
promptTitle,
|
||||
displayTextForListItem,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
displayTextForListItem: (queryItem: UpdateBranchFragment) => string;
|
||||
promptTitle: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
): Promise<UpdateBranchFragment> {
|
||||
export async function selectBranchOnAppAsync({
|
||||
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.');
|
||||
}
|
||||
@@ -36,8 +32,7 @@ export async function selectBranchOnAppAsync(
|
||||
const selectedBranch = await paginatedQueryWithSelectPromptAsync({
|
||||
limit: paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
||||
queryToPerform: (limit, offset) => queryBranchesOnProjectAsync(limit, offset, projectId),
|
||||
promptOptions: {
|
||||
title: promptTitle,
|
||||
getIdentifierForQueryItem: updateBranchFragment => updateBranchFragment.id,
|
||||
@@ -50,19 +45,15 @@ export async function selectBranchOnAppAsync(
|
||||
return selectedBranch;
|
||||
}
|
||||
|
||||
export async function listAndRenderBranchesOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
): Promise<void> {
|
||||
export async function listAndRenderBranchesOnAppAsync({
|
||||
projectId,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}): Promise<void> {
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const branches = await queryBranchesOnProjectAsync(
|
||||
graphqlClient,
|
||||
paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
|
||||
paginatedQueryOptions.offset,
|
||||
projectId
|
||||
@@ -72,8 +63,7 @@ export async function listAndRenderBranchesOnAppAsync(
|
||||
await paginatedQueryWithConfirmPromptAsync({
|
||||
limit: paginatedQueryOptions.limit ?? BRANCHES_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
queryBranchesOnProjectAsync(graphqlClient, limit, offset, projectId),
|
||||
queryToPerform: (limit, offset) => queryBranchesOnProjectAsync(limit, offset, projectId),
|
||||
promptOptions: {
|
||||
title: 'Load more branches?',
|
||||
renderListItems: branches => renderPageOfBranches(branches, paginatedQueryOptions),
|
||||
@@ -83,12 +73,11 @@ export async function listAndRenderBranchesOnAppAsync(
|
||||
}
|
||||
|
||||
async function queryBranchesOnProjectAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
limit: number,
|
||||
offset: number,
|
||||
projectId: string
|
||||
): Promise<UpdateBranchFragment[]> {
|
||||
return await BranchQuery.listBranchesOnAppAsync(graphqlClient, {
|
||||
return await BranchQuery.listBranchesOnAppAsync({
|
||||
appId: projectId,
|
||||
limit,
|
||||
offset,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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';
|
||||
@@ -45,18 +43,14 @@ 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(graphqlClient, projectId, 'blah')).resolves.toEqual(null);
|
||||
expect(selectBuildToCancelAsync(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(graphqlClient, projectId, 'blah')).resolves.toEqual(
|
||||
selectedBuildId
|
||||
);
|
||||
expect(selectBuildToCancelAsync(projectId, 'blah')).resolves.toEqual(selectedBuildId);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from '../../project/android/applicationId';
|
||||
import { resolveGradleBuildContextAsync } from '../../project/android/gradle';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import {
|
||||
BuildRequestSender,
|
||||
CredentialsResult,
|
||||
@@ -57,13 +58,13 @@ This means that it will most likely produce an AAB and you will not be able to i
|
||||
const gradleContext = await resolveGradleBuildContextAsync(ctx.projectDir, buildProfile);
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
||||
}
|
||||
|
||||
const applicationId = await getApplicationIdAsync(ctx.projectDir, ctx.exp, gradleContext);
|
||||
const versionCodeOverride =
|
||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||
? await resolveRemoteVersionCodeAsync(ctx.graphqlClient, {
|
||||
? await resolveRemoteVersionCodeAsync({
|
||||
projectDir: ctx.projectDir,
|
||||
projectId: ctx.projectId,
|
||||
exp: ctx.exp,
|
||||
@@ -84,14 +85,14 @@ export async function prepareAndroidBuildAsync(
|
||||
return await ensureAndroidCredentialsAsync(ctx);
|
||||
},
|
||||
syncProjectConfigurationAsync: async () => {
|
||||
await syncProjectConfigurationAsync(ctx.graphqlClient, {
|
||||
await syncProjectConfigurationAsync({
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
localAutoIncrement:
|
||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
projectId: ctx.projectId,
|
||||
nonInteractive: ctx.nonInteractive,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
@@ -108,7 +109,7 @@ export async function prepareAndroidBuildAsync(
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createAndroidBuildAsync(ctx.graphqlClient, {
|
||||
return await BuildMutation.createAndroidBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
@@ -136,7 +137,7 @@ async function ensureAndroidCredentialsAsync(
|
||||
const provider = new AndroidCredentialsProvider(ctx.credentialsCtx, {
|
||||
app: {
|
||||
account: nullthrows(
|
||||
ctx.user.accounts.find(a => a.name === ctx.accountName),
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
|
||||
@@ -5,6 +5,7 @@ import slash from 'slash';
|
||||
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
@@ -23,7 +24,10 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
const username = getUsername(ctx.exp, ctx.user);
|
||||
const username = getUsername(
|
||||
ctx.exp,
|
||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
||||
);
|
||||
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
|
||||
@@ -6,34 +6,30 @@ 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(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectDir,
|
||||
exp,
|
||||
localAutoIncrement,
|
||||
projectId,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
localAutoIncrement?: AndroidVersionAutoIncrement;
|
||||
projectId: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
export async function syncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
localAutoIncrement,
|
||||
nonInteractive,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
localAutoIncrement?: AndroidVersionAutoIncrement;
|
||||
nonInteractive: boolean;
|
||||
}): 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(graphqlClient, projectDir, exp, projectId);
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp, { nonInteractive });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,6 @@ 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';
|
||||
@@ -175,24 +174,20 @@ 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(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectDir,
|
||||
projectId,
|
||||
exp,
|
||||
applicationId,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
applicationId: string;
|
||||
buildProfile: BuildProfile<Platform.ANDROID>;
|
||||
}
|
||||
): Promise<string> {
|
||||
export async function resolveRemoteVersionCodeAsync({
|
||||
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
|
||||
@@ -222,7 +217,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
`Initializing versionCode with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
try {
|
||||
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
@@ -244,7 +239,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
)}.`
|
||||
).start();
|
||||
try {
|
||||
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
|
||||
@@ -8,7 +8,6 @@ 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,
|
||||
@@ -19,7 +18,7 @@ import {
|
||||
} from '../graphql/generated';
|
||||
import { BuildResult } from '../graphql/mutations/BuildMutation';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log, { learnMore, link } from '../log';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { Ora, ora } from '../ora';
|
||||
import {
|
||||
appPlatformDisplayNames,
|
||||
@@ -141,34 +140,12 @@ function handleBuildRequestError(error: any, platform: Platform): never {
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
|
||||
) {
|
||||
Log.error(
|
||||
`EAS Build is down for maintenance. Try again later. Check ${link(
|
||||
'https://status.expo.dev/'
|
||||
)} for updates.`
|
||||
'EAS Build is down for maintenance. Try again later. Check https://status.expo.dev/ for updates.'
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED') {
|
||||
Log.error(
|
||||
`EAS Build free tier is temporarily disabled and we are not accepting any new builds. Try again later. ${learnMore(
|
||||
'https://expo.fyi/eas-build-queues'
|
||||
)}`
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED_IOS'
|
||||
) {
|
||||
Log.error(
|
||||
`EAS Build free tier is temporarily disabled for iOS and we are not accepting any new builds. Try again later. ${learnMore(
|
||||
'https://expo.fyi/eas-build-queues'
|
||||
)}`
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED_ANDROID'
|
||||
) {
|
||||
Log.error(
|
||||
`EAS Build free tier is temporarily disabled for Android and we are not accepting any new builds. Try again later. ${learnMore(
|
||||
'https://expo.fyi/eas-build-queues'
|
||||
)}`
|
||||
'EAS Build free tier is temporarily disabled. Try again later. Check https://status.expo.dev/ for updates.'
|
||||
);
|
||||
throw new Error('Build request failed.');
|
||||
} else if (
|
||||
@@ -214,7 +191,6 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
projectTarballPath = projectTarball.path;
|
||||
|
||||
const { bucketKey } = await uploadFileAtPathToS3Async(
|
||||
ctx.graphqlClient,
|
||||
UploadSessionType.EasBuildProjectSources,
|
||||
projectTarball.path,
|
||||
createProgressTracker({
|
||||
@@ -277,7 +253,6 @@ 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[]> {
|
||||
@@ -292,7 +267,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(graphqlClient, buildIds);
|
||||
const builds = await getBuildsSafelyAsync(buildIds);
|
||||
const { refetch } =
|
||||
builds.length === 1
|
||||
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
|
||||
@@ -304,13 +279,10 @@ export async function waitForBuildEndAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function getBuildsSafelyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
buildIds: string[]
|
||||
): Promise<MaybeBuildFragment[]> {
|
||||
async function getBuildsSafelyAsync(buildIds: string[]): Promise<MaybeBuildFragment[]> {
|
||||
const promises = buildIds.map(async buildId => {
|
||||
try {
|
||||
return await BuildQuery.byIdAsync(graphqlClient, buildId, { useCache: false });
|
||||
return await BuildQuery.byIdAsync(buildId, { useCache: false });
|
||||
} catch (err) {
|
||||
Log.debug('Failed to fetch the build status', err);
|
||||
return null;
|
||||
|
||||
@@ -3,7 +3,6 @@ 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';
|
||||
@@ -48,7 +47,6 @@ 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;
|
||||
|
||||
@@ -7,13 +7,12 @@ 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';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getOwnerAccountForProjectIdAsync, getProjectIdAsync } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { createAndroidContextAsync } from './android/build';
|
||||
import { BuildContext, CommonContext } from './context';
|
||||
import { createIosContextAsync } from './ios/build';
|
||||
@@ -31,9 +30,6 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
projectDir,
|
||||
resourceClass,
|
||||
message,
|
||||
actor,
|
||||
graphqlClient,
|
||||
getDynamicProjectConfigAsync,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
@@ -46,24 +42,25 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
projectDir: string;
|
||||
resourceClass: BuildResourceClass;
|
||||
message?: string;
|
||||
actor: Actor;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
getDynamicProjectConfigAsync: DynamicConfigContextFn;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
|
||||
const exp = getExpoConfig(projectDir, { env: buildProfile.env });
|
||||
|
||||
const user = await ensureLoggedInAsync({ nonInteractive });
|
||||
const projectName = exp.slug;
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectId = await getProjectIdAsync(exp, {
|
||||
env: buildProfile.env,
|
||||
nonInteractive,
|
||||
});
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, platform);
|
||||
const accountId = account.id;
|
||||
const runFromCI = getenv.boolish('CI', false);
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
projectInfo: { exp, projectId },
|
||||
exp,
|
||||
nonInteractive,
|
||||
projectDir,
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
user,
|
||||
env: buildProfile.env,
|
||||
easJsonCliConfig,
|
||||
});
|
||||
@@ -102,8 +99,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
projectId,
|
||||
projectName,
|
||||
trackingCtx,
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
user,
|
||||
workflow,
|
||||
message,
|
||||
runFromCI,
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function createIosContextAsync(
|
||||
const { buildProfile } = ctx;
|
||||
|
||||
if (ctx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp, ctx.user);
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(ctx.projectDir, ctx.exp);
|
||||
}
|
||||
|
||||
checkNodeEnvVariable(ctx);
|
||||
@@ -46,7 +46,7 @@ export async function createIosContextAsync(
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const buildNumberOverride =
|
||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||
? await resolveRemoteBuildNumberAsync(ctx.graphqlClient, {
|
||||
? await resolveRemoteBuildNumberAsync({
|
||||
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(ctx.graphqlClient, {
|
||||
await syncProjectConfigurationAsync({
|
||||
projectDir: ctx.projectDir,
|
||||
exp: ctx.exp,
|
||||
targets: ctx.ios.targets,
|
||||
@@ -80,7 +80,7 @@ export async function prepareIosBuildAsync(
|
||||
ctx.easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
projectId: ctx.projectId,
|
||||
nonInteractive: ctx.nonInteractive,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
@@ -100,7 +100,7 @@ export async function prepareIosBuildAsync(
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createIosBuildAsync(ctx.graphqlClient, {
|
||||
return await BuildMutation.createIosBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
|
||||
@@ -4,6 +4,7 @@ import slash from 'slash';
|
||||
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
|
||||
@@ -25,7 +26,10 @@ export async function prepareJobAsync(
|
||||
): Promise<Job> {
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await getVcsClient().getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
const username = getUsername(ctx.exp, ctx.user);
|
||||
const username = getUsername(
|
||||
ctx.exp,
|
||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
||||
);
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
const targetNames = Object.keys(jobData.credentials);
|
||||
|
||||
@@ -2,35 +2,31 @@ 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(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectDir,
|
||||
exp,
|
||||
targets,
|
||||
localAutoIncrement,
|
||||
projectId,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
targets: Target[];
|
||||
localAutoIncrement?: IosVersionAutoIncrement;
|
||||
projectId: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
export async function syncProjectConfigurationAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
targets,
|
||||
localAutoIncrement,
|
||||
nonInteractive,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
targets: Target[];
|
||||
localAutoIncrement?: IosVersionAutoIncrement;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp, { nonInteractive });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,6 @@ 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';
|
||||
@@ -256,24 +255,20 @@ 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(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectDir,
|
||||
projectId,
|
||||
exp,
|
||||
applicationTarget,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
applicationTarget: Target;
|
||||
buildProfile: BuildProfile<Platform.IOS>;
|
||||
}
|
||||
): Promise<string> {
|
||||
export async function resolveRemoteBuildNumberAsync({
|
||||
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
|
||||
@@ -312,7 +307,7 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
`Initializing buildNumber with ${chalk.bold(currentBuildVersion)}.`
|
||||
).start();
|
||||
try {
|
||||
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Ios,
|
||||
applicationIdentifier: applicationTarget.bundleIdentifier,
|
||||
@@ -334,7 +329,7 @@ export async function resolveRemoteBuildNumberAsync(
|
||||
)}.`
|
||||
).start();
|
||||
try {
|
||||
await AppVersionMutation.createAppVersionAsync(graphqlClient, {
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Ios,
|
||||
applicationIdentifier: applicationTarget.bundleIdentifier,
|
||||
|
||||
@@ -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.99';
|
||||
|
||||
export interface LocalBuildOptions {
|
||||
enable: boolean;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
||||
} from '../update/ios/UpdatesModule';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { maybeResolveVersionsAsync as maybeResolveAndroidVersionsAsync } from './android/version';
|
||||
@@ -48,7 +49,10 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
(await vcsClient.getLastCommitMessageAsync()) ?? undefined
|
||||
),
|
||||
isGitWorkingTreeDirty: await vcsClient.hasUncommittedChangesAsync(),
|
||||
username: getUsername(ctx.exp, ctx.user),
|
||||
username: getUsername(
|
||||
ctx.exp,
|
||||
await ensureLoggedInAsync({ nonInteractive: ctx.nonInteractive })
|
||||
),
|
||||
message: ctx.message,
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -11,22 +10,19 @@ import { formatGraphQLBuild } from './utils/formatBuild';
|
||||
|
||||
export const BUILDS_LIMIT = 50;
|
||||
|
||||
export async function listAndRenderBuildsOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
projectDisplayName,
|
||||
filter,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
projectDisplayName: string;
|
||||
filter?: BuildFilter;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
): Promise<void> {
|
||||
export async function listAndRenderBuildsOnAppAsync({
|
||||
projectId,
|
||||
projectDisplayName,
|
||||
filter,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
projectDisplayName: string;
|
||||
filter?: BuildFilter;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}): Promise<void> {
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
const builds = await BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
@@ -38,7 +34,7 @@ export async function listAndRenderBuildsOnAppAsync(
|
||||
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
limit,
|
||||
offset,
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
@@ -43,8 +41,6 @@ import {
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
|
||||
import { Actor } from '../user/User';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
||||
import { getVcsClient } from '../vcs';
|
||||
@@ -77,50 +73,21 @@ export interface BuildFlags {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const iosUserInputResourceClassToBuildResourceClassMapping: Record<
|
||||
UserInputResourceClass,
|
||||
BuildResourceClass
|
||||
const platformToGraphQLResourceClassMapping: Record<
|
||||
Platform,
|
||||
Record<UserInputResourceClass, BuildResourceClass>
|
||||
> = {
|
||||
[UserInputResourceClass.DEFAULT]: BuildResourceClass.IosDefault,
|
||||
[UserInputResourceClass.LARGE]: BuildResourceClass.IosLarge,
|
||||
[UserInputResourceClass.M1_EXPERIMENTAL]: BuildResourceClass.IosM1Large,
|
||||
[Platform.ANDROID]: {
|
||||
[UserInputResourceClass.DEFAULT]: BuildResourceClass.AndroidDefault,
|
||||
[UserInputResourceClass.LARGE]: BuildResourceClass.AndroidLarge,
|
||||
},
|
||||
[Platform.IOS]: {
|
||||
[UserInputResourceClass.DEFAULT]: BuildResourceClass.IosDefault,
|
||||
[UserInputResourceClass.LARGE]: BuildResourceClass.IosLarge,
|
||||
},
|
||||
};
|
||||
|
||||
const androidUserInputResourceClassToBuildResourceClassMapping: Record<
|
||||
Exclude<UserInputResourceClass, UserInputResourceClass.M1_EXPERIMENTAL>,
|
||||
BuildResourceClass
|
||||
> = {
|
||||
[UserInputResourceClass.DEFAULT]: BuildResourceClass.AndroidDefault,
|
||||
[UserInputResourceClass.LARGE]: BuildResourceClass.AndroidLarge,
|
||||
};
|
||||
|
||||
function resolveResourceClass(
|
||||
platform: Platform,
|
||||
resourceClassInput: UserInputResourceClass
|
||||
): BuildResourceClass {
|
||||
if (platform !== Platform.IOS && resourceClassInput === UserInputResourceClass.M1_EXPERIMENTAL) {
|
||||
throw new Error(
|
||||
`Resource class ${UserInputResourceClass.M1_EXPERIMENTAL} is only available for iOS builds`
|
||||
);
|
||||
}
|
||||
|
||||
return platform === Platform.ANDROID
|
||||
? androidUserInputResourceClassToBuildResourceClassMapping[
|
||||
resourceClassInput as Exclude<
|
||||
UserInputResourceClass,
|
||||
UserInputResourceClass.M1_EXPERIMENTAL
|
||||
>
|
||||
]
|
||||
: iosUserInputResourceClassToBuildResourceClassMapping[resourceClassInput];
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectDir: string,
|
||||
flags: BuildFlags,
|
||||
actor: Actor,
|
||||
getDynamicProjectConfigAsync: DynamicConfigContextFn
|
||||
): Promise<void> {
|
||||
export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFlags): Promise<void> {
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
@@ -162,14 +129,11 @@ export async function runBuildAndSubmitAsync(
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
resourceClass: resolveResourceClass(
|
||||
buildProfile.platform,
|
||||
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
|
||||
),
|
||||
resourceClass:
|
||||
platformToGraphQLResourceClassMapping[buildProfile.platform][
|
||||
flags.userInputResourceClass ?? UserInputResourceClass.DEFAULT
|
||||
],
|
||||
easJsonCliConfig,
|
||||
actor,
|
||||
graphqlClient,
|
||||
getDynamicProjectConfigAsync,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
@@ -208,10 +172,7 @@ export async function runBuildAndSubmitAsync(
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(
|
||||
graphqlClient,
|
||||
startedBuild.build.id
|
||||
);
|
||||
startedBuild.build = await BuildQuery.withSubmissionsByIdAsync(startedBuild.build.id);
|
||||
submissions.push(submission);
|
||||
}
|
||||
|
||||
@@ -228,7 +189,7 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
|
||||
const { accountName } = Object.values(buildCtxByPlatform)[0];
|
||||
const builds = await waitForBuildEndAsync(graphqlClient, {
|
||||
const builds = await waitForBuildEndAsync({
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
accountName,
|
||||
});
|
||||
@@ -245,16 +206,13 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
|
||||
} else {
|
||||
const completedSubmissions = await waitForSubmissionsToCompleteAsync(
|
||||
graphqlClient,
|
||||
submissions
|
||||
);
|
||||
const completedSubmissions = await waitForSubmissionsToCompleteAsync(submissions);
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(
|
||||
await Promise.all(
|
||||
builds
|
||||
.filter((i): i is BuildWithSubmissionsFragment => !!i)
|
||||
.map(build => BuildQuery.withSubmissionsByIdAsync(graphqlClient, build.id))
|
||||
.map(build => BuildQuery.withSubmissionsByIdAsync(build.id))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -269,9 +227,6 @@ async function prepareAndStartBuildAsync({
|
||||
buildProfile,
|
||||
resourceClass,
|
||||
easJsonCliConfig,
|
||||
actor,
|
||||
graphqlClient,
|
||||
getDynamicProjectConfigAsync,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
@@ -279,9 +234,6 @@ async function prepareAndStartBuildAsync({
|
||||
buildProfile: ProfileData<'build'>;
|
||||
resourceClass: BuildResourceClass;
|
||||
easJsonCliConfig: EasJson['cli'];
|
||||
actor: Actor;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
getDynamicProjectConfigAsync: DynamicConfigContextFn;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
@@ -295,9 +247,6 @@ async function prepareAndStartBuildAsync({
|
||||
localBuildOptions: flags.localBuildOptions,
|
||||
easJsonCliConfig,
|
||||
message: flags.message,
|
||||
actor,
|
||||
graphqlClient,
|
||||
getDynamicProjectConfigAsync,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -309,14 +258,6 @@ async function prepareAndStartBuildAsync({
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
await validateBuildProfileConfigMatchesProjectConfigAsync(
|
||||
buildCtx.exp,
|
||||
buildProfile,
|
||||
buildCtx.projectId,
|
||||
flags.nonInteractive
|
||||
);
|
||||
|
||||
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
|
||||
if (easJsonCliConfig?.appVersionSource === AppVersionSource.REMOTE) {
|
||||
validateAppConfigForRemoteVersionSource(buildCtx.exp, buildProfile.platform);
|
||||
@@ -370,16 +311,13 @@ async function prepareAndStartSubmissionAsync({
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId: build.project.id,
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
actor: buildCtx.user,
|
||||
graphqlClient: buildCtx.graphqlClient,
|
||||
projectId: buildCtx.projectId,
|
||||
exp: buildCtx.exp,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
|
||||
@@ -16,9 +16,4 @@ export enum BuildDistributionType {
|
||||
export enum UserInputResourceClass {
|
||||
DEFAULT = 'default',
|
||||
LARGE = 'large',
|
||||
/**
|
||||
* @experimental
|
||||
* This resource class is not yet ready to be used in production. For testing purposes only. Might be deprecated / deleted at any time.
|
||||
*/
|
||||
M1_EXPERIMENTAL = 'm1-experimental',
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { PaginatedQueryOptions } from '../commandUtils/pagination';
|
||||
import {
|
||||
ViewBranchesOnUpdateChannelQueryVariables,
|
||||
@@ -19,18 +18,15 @@ import { logChannelDetails } from './utils';
|
||||
|
||||
export const CHANNELS_LIMIT = 25;
|
||||
|
||||
export async function selectChannelOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
selectionPromptTitle,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
selectionPromptTitle: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
): Promise<UpdateChannelObject> {
|
||||
export async function selectChannelOnAppAsync({
|
||||
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.');
|
||||
}
|
||||
@@ -38,8 +34,7 @@ export async function selectChannelOnAppAsync(
|
||||
const updateChannel = await paginatedQueryWithSelectPromptAsync({
|
||||
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
queryChannelsOnAppAsync(graphqlClient, { appId: projectId, limit, offset }),
|
||||
queryToPerform: (limit, offset) => queryChannelsOnAppAsync({ appId: projectId, limit, offset }),
|
||||
promptOptions: {
|
||||
title: selectionPromptTitle,
|
||||
createDisplayTextForSelectionPromptListItem: updateChannel => updateChannel.name,
|
||||
@@ -53,18 +48,15 @@ export async function selectChannelOnAppAsync(
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
export async function listAndRenderChannelsOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
): Promise<void> {
|
||||
export async function listAndRenderChannelsOnAppAsync({
|
||||
projectId,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}): Promise<void> {
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const channels = await queryChannelsOnAppAsync(graphqlClient, {
|
||||
const channels = await queryChannelsOnAppAsync({
|
||||
appId: projectId,
|
||||
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
@@ -75,7 +67,7 @@ export async function listAndRenderChannelsOnAppAsync(
|
||||
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
queryChannelsOnAppAsync(graphqlClient, { limit, offset, appId: projectId }),
|
||||
queryChannelsOnAppAsync({ limit, offset, appId: projectId }),
|
||||
promptOptions: {
|
||||
title: 'Load more channels?',
|
||||
renderListItems: channels => renderPageOfChannels(channels, paginatedQueryOptions),
|
||||
@@ -84,23 +76,20 @@ export async function listAndRenderChannelsOnAppAsync(
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
export async function listAndRenderBranchesAndUpdatesOnChannelAsync({
|
||||
projectId: appId,
|
||||
channelName,
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
channelName: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}): Promise<void> {
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync({ appId, channelName });
|
||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
||||
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
||||
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync({
|
||||
appId,
|
||||
channelName,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
@@ -112,7 +101,7 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
limit: paginatedQueryOptions.limit ?? CHANNELS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
||||
queryBranchesAndUpdateGroupsOnChannelAsync({
|
||||
channelName,
|
||||
appId,
|
||||
offset,
|
||||
@@ -127,11 +116,12 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function queryChannelsOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, offset, limit }: ViewUpdateChannelsOnAppQueryVariables
|
||||
): Promise<UpdateChannelObject[]> {
|
||||
return await ChannelQuery.viewUpdateChannelsOnAppAsync(graphqlClient, {
|
||||
async function queryChannelsOnAppAsync({
|
||||
appId,
|
||||
offset,
|
||||
limit,
|
||||
}: ViewUpdateChannelsOnAppQueryVariables): Promise<UpdateChannelObject[]> {
|
||||
return await ChannelQuery.viewUpdateChannelsOnAppAsync({
|
||||
appId,
|
||||
offset,
|
||||
limit,
|
||||
@@ -139,10 +129,9 @@ async function queryChannelsOnAppAsync(
|
||||
}
|
||||
|
||||
async function queryBranchesAndUpdateGroupsOnChannelAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
args: ViewBranchesOnUpdateChannelQueryVariables
|
||||
): Promise<UpdateBranchOnChannelObject[]> {
|
||||
return await BranchQuery.listBranchesOnChannelAsync(graphqlClient, args);
|
||||
return await BranchQuery.listBranchesOnChannelAsync(args);
|
||||
}
|
||||
|
||||
function renderPageOfChannels(
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import * as PackageManagerUtils from '@expo/package-manager';
|
||||
import { Command } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
|
||||
import {
|
||||
AnalyticsEvent,
|
||||
@@ -6,126 +12,21 @@ import {
|
||||
initAsync as initAnalyticsAsync,
|
||||
logEvent,
|
||||
} from '../analytics/rudderstackClient';
|
||||
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 {
|
||||
[name: string]: any;
|
||||
} = object
|
||||
> = {
|
||||
[P in keyof T]: ContextField<T[P]>;
|
||||
};
|
||||
|
||||
type ContextOutput<
|
||||
T extends {
|
||||
[name: string]: any;
|
||||
} = object
|
||||
> = {
|
||||
[P in keyof T]: T[P];
|
||||
};
|
||||
import { learnMore } from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { getUserAsync } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { setVcsClient } from '../vcs';
|
||||
import GitClient from '../vcs/clients/git';
|
||||
|
||||
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 and a logged-in
|
||||
* graphql client in the context.
|
||||
*/
|
||||
LoggedIn: {
|
||||
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
|
||||
* run within a project directory, null otherwise.
|
||||
*/
|
||||
OptionalProjectConfig: {
|
||||
projectConfig: new OptionalProjectConfigContextField(),
|
||||
},
|
||||
/**
|
||||
* Require this command to be run in a project directory. Return the project directory in the context.
|
||||
*/
|
||||
ProjectDir: {
|
||||
projectDir: new ProjectDirContextField(),
|
||||
},
|
||||
/**
|
||||
* Provides functions to load the project config when dynamic config options are needed (custom Env for example).
|
||||
*/
|
||||
DynamicProjectConfig: {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
getDynamicProjectConfigAsync: new DynamicProjectConfigContextField(),
|
||||
},
|
||||
/**
|
||||
* Require the project to be identified and registered on server. Returns the project config in the context.
|
||||
*/
|
||||
ProjectConfig: {
|
||||
projectConfig: new ProjectConfigContextField(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Context allows for subclasses (commands) to declare their prerequisites in a type-safe manner.
|
||||
* These declarative definitions each output a context property that is the result of the prerequisite being
|
||||
* satisfied. These allow a unified common interface to be shared amongst commands in order to provide a more
|
||||
* consistent CLI experience.
|
||||
*
|
||||
* For example, let's say a command needs the EAS project ID to make a GraphQL mutation. It should declare that
|
||||
* it requires the `ProjectConfig` context, and then call `getContextAsync` to get the project ID.
|
||||
* When user data is unavailable locally, determines if the command will
|
||||
* force the user to log in
|
||||
*/
|
||||
static contextDefinition: ContextInput = {};
|
||||
|
||||
/**
|
||||
* Execute the context in the contextDefinition to satisfy command prerequisites.
|
||||
*/
|
||||
protected async getContextAsync<
|
||||
C extends {
|
||||
[name: string]: any;
|
||||
} = object
|
||||
>(
|
||||
commandClass: { contextDefinition: ContextInput<C> },
|
||||
{ nonInteractive }: { nonInteractive: boolean }
|
||||
): Promise<ContextOutput<C>> {
|
||||
const contextDefinition = commandClass.contextDefinition;
|
||||
|
||||
const contextValuePairs = await Promise.all(
|
||||
Object.keys(contextDefinition).map(async contextKey => {
|
||||
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 requiresAuthentication = true;
|
||||
protected mustBeRunInsideProject = true;
|
||||
|
||||
protected abstract runAsync(): Promise<any>;
|
||||
|
||||
@@ -133,15 +34,24 @@ export default abstract class EasCommand extends Command {
|
||||
async run(): Promise<any> {
|
||||
await initAnalyticsAsync();
|
||||
|
||||
// this is needed for logEvent call below as it identifies the user in the analytics system
|
||||
await this.sessionManager.getUserAsync();
|
||||
if (this.mustBeRunInsideProject) {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await this.applyCliConfigAsync(projectDir);
|
||||
await this.ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
}
|
||||
|
||||
if (this.requiresAuthentication) {
|
||||
const { flags } = await this.parse();
|
||||
const nonInteractive = (flags as any)['non-interactive'] ?? false;
|
||||
await ensureLoggedInAsync({ nonInteractive });
|
||||
} else {
|
||||
await getUserAsync();
|
||||
}
|
||||
logEvent(AnalyticsEvent.ACTION, {
|
||||
// id is assigned by oclif in constructor based on the filepath:
|
||||
// commands/submit === submit, commands/build/list === build:list
|
||||
action: `eas ${this.id}`,
|
||||
});
|
||||
|
||||
return this.runAsync();
|
||||
}
|
||||
|
||||
@@ -150,4 +60,63 @@ export default abstract class EasCommand extends Command {
|
||||
await flushAnalyticsAsync();
|
||||
return super.finally(err);
|
||||
}
|
||||
|
||||
private async applyCliConfigAsync(projectDir: string): Promise<void> {
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
);
|
||||
}
|
||||
if (config?.requireCommit) {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise<void> {
|
||||
let printCliVersionWarning = false;
|
||||
|
||||
const consoleWarn = (msg?: string): void => {
|
||||
if (msg) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(chalk.yellow(msg));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn();
|
||||
}
|
||||
};
|
||||
|
||||
if (await this.isEasCliInDependenciesAsync(projectDir)) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||
}
|
||||
|
||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||
if (maybeRepoRoot !== projectDir && (await this.isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||
}
|
||||
|
||||
if (printCliVersionWarning) {
|
||||
consoleWarn(
|
||||
`It's recommended to use the ${chalk.bold(
|
||||
'"cli.version"'
|
||||
)} field in eas.json to enforce the ${chalk.bold('eas-cli')} version for your project.`
|
||||
);
|
||||
consoleWarn(
|
||||
learnMore('https://github.com/expo/eas-cli#enforcing-eas-cli-version-for-your-project')
|
||||
);
|
||||
consoleWarn();
|
||||
}
|
||||
}
|
||||
|
||||
private async isEasCliInDependenciesAsync(dir: string): Promise<boolean> {
|
||||
const packageJsonPath = path.join(dir, 'package.json');
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
return (
|
||||
packageJson?.dependencies?.['eas-cli'] !== undefined ||
|
||||
packageJson?.devDependencies?.['eas-cli'] !== undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
||||
import SessionManager from '../../user/SessionManager';
|
||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||
import { getUserAsync } from '../../user/User';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import EasCommand from '../EasCommand';
|
||||
|
||||
jest.mock('../../user/User');
|
||||
jest.mock('../../user/SessionManager');
|
||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn() }));
|
||||
jest.mock('../../analytics/rudderstackClient', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
||||
return {
|
||||
@@ -26,13 +28,23 @@ afterAll(() => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.mocked(getUserAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(async () => mockJester);
|
||||
jest
|
||||
.mocked(ensureLoggedInAsync)
|
||||
.mockReset()
|
||||
.mockImplementation(async () => mockJester);
|
||||
jest.mocked(initAsync).mockReset();
|
||||
jest.mocked(flushAsync).mockReset();
|
||||
jest.mocked(logEvent).mockReset();
|
||||
});
|
||||
|
||||
const createTestEasCommand = (): typeof EasCommand => {
|
||||
const createTestEasCommand = (authValue: boolean): typeof EasCommand => {
|
||||
class TestEasCommand extends EasCommand {
|
||||
override requiresAuthentication = authValue;
|
||||
|
||||
async runAsync(): Promise<void> {}
|
||||
}
|
||||
|
||||
@@ -49,30 +61,37 @@ describe(EasCommand.name, () => {
|
||||
//
|
||||
// See https://github.com/oclif/command/blob/master/src/command.ts#L80
|
||||
// and look for "Config.load"
|
||||
it('ensures the user data is read', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
it('ensures the user is logged in', async () => {
|
||||
const TestEasCommand = createTestEasCommand(true);
|
||||
await TestEasCommand.run();
|
||||
|
||||
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
||||
expect(sessionManagerSpy).toBeCalledTimes(1);
|
||||
expect(ensureLoggedInAsync).toHaveBeenCalled();
|
||||
}, 15_000);
|
||||
|
||||
it('ensures the user data is read from cache', async () => {
|
||||
const TestEasCommand = createTestEasCommand(false);
|
||||
await TestEasCommand.run();
|
||||
|
||||
expect(jest.mocked(getUserAsync)).toBeCalledTimes(1);
|
||||
expect(jest.mocked(ensureLoggedInAsync)).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('initializes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const TestEasCommand = createTestEasCommand(true);
|
||||
await TestEasCommand.run();
|
||||
|
||||
expect(initAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('flushes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const TestEasCommand = createTestEasCommand(true);
|
||||
await TestEasCommand.run();
|
||||
|
||||
expect(flushAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('logs events', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const TestEasCommand = createTestEasCommand(true);
|
||||
await TestEasCommand.run();
|
||||
|
||||
expect(logEvent).toHaveBeenCalledWith('action', {
|
||||
@@ -83,7 +102,7 @@ describe(EasCommand.name, () => {
|
||||
|
||||
describe('after exceptions', () => {
|
||||
it('flushes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const TestEasCommand = createTestEasCommand(true);
|
||||
try {
|
||||
await TestEasCommand.run().then(() => {
|
||||
throw new Error('foo');
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import SessionManager from '../../user/SessionManager';
|
||||
|
||||
export interface ContextOptions {
|
||||
sessionManager: SessionManager;
|
||||
nonInteractive: boolean;
|
||||
}
|
||||
|
||||
export default abstract class ContextField<T> {
|
||||
abstract getValueAsync(options: ContextOptions): Promise<T>;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
|
||||
import { ExpoConfigOptions, getExpoConfig } from '../../project/expoConfig';
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
|
||||
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}>;
|
||||
|
||||
export class DynamicProjectConfigContextField extends ContextField<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(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
env: options?.env,
|
||||
});
|
||||
const exp = getExpoConfig(projectDir, options);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
projectId,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
|
||||
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
|
||||
export class OptionalProjectConfigContextField extends ContextField<
|
||||
| {
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
|
||||
| {
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
let projectDir: string;
|
||||
try {
|
||||
projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
if (!projectDir) {
|
||||
return undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof InvalidEasJsonError) {
|
||||
throw e;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const expBefore = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getExpoConfig(projectDir);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
projectId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
|
||||
export default class ProjectConfigContextField extends ContextField<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
const expBefore = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
return {
|
||||
projectId,
|
||||
exp,
|
||||
projectDir,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import ContextField from './ContextField';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
|
||||
export default class ProjectDirContextField extends ContextField<string> {
|
||||
async getValueAsync(): Promise<string> {
|
||||
return await findProjectDirAndVerifyProjectSetupAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
|
||||
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe(findProjectRootAsync, () => {
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
it('throws if not inside the project directory', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./README.md': '1',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
await expect(findProjectRootAsync({ cwd: '/app' })).rejects.toThrow(
|
||||
'Run this command inside a project directory.'
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to process.cwd() if defaultToProcessCwd = true', async () => {
|
||||
const spy = jest.spyOn(process, 'cwd').mockReturnValue('/this/is/fake/process/cwd');
|
||||
try {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./README.md': '1',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
const projectDir = await findProjectRootAsync({
|
||||
cwd: '/app',
|
||||
defaultToProcessCwd: true,
|
||||
});
|
||||
expect(projectDir).toBe('/this/is/fake/process/cwd');
|
||||
} finally {
|
||||
spy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the root directory of the project', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./README.md': '1',
|
||||
'./package.json': '2',
|
||||
'./src/index.ts': '3',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
const projectRoot = await findProjectRootAsync({ cwd: '/app/src' });
|
||||
expect(projectRoot).toBe('/app');
|
||||
});
|
||||
});
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
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 SessionManager from '../../../../user/SessionManager';
|
||||
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from '../getProjectIdAsync';
|
||||
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('../../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../contextUtils/findProjectDirAndVerifyProjectSetupAsync');
|
||||
jest.mock('../../../../user/User');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: () => ({ succeed: () => {}, fail: () => {} }),
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||
|
||||
describe(getProjectIdAsync, () => {
|
||||
let sessionManager: SessionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
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' } }],
|
||||
},
|
||||
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(
|
||||
{
|
||||
'./README.md': '1',
|
||||
'./package.json': '2',
|
||||
'./src/index.ts': '3',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
|
||||
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
|
||||
});
|
||||
|
||||
it('gets the project ID from app config if exists', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toEqual('1234');
|
||||
});
|
||||
|
||||
it('throws when the owner is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test', owner: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Project identified by "extra.eas.projectId" (notnotbrent) is not owned by owner specified in the "owner" field (wat). ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the slug is out of sync', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({
|
||||
exp: { name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
} as any);
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'wat', extra: { eas: { projectId: '1234' } } },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Project config: Slug for project identified by "extra.eas.projectId" (test) does not match the "slug" field (wat). ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('fetches the project ID when not in app config, and sets it in the config', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'success',
|
||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
.mockImplementation(async () => '2345');
|
||||
|
||||
const projectId = await getProjectIdAsync(
|
||||
sessionManager,
|
||||
{ name: 'test', slug: 'test' },
|
||||
{
|
||||
nonInteractive: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(projectId).toEqual('2345');
|
||||
|
||||
expect(modifyConfigAsync).toHaveBeenCalledTimes(1);
|
||||
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', {
|
||||
extra: { eas: { projectId: '2345' } },
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if writing the ID back to the config fails', async () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'fail',
|
||||
config: null,
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
.mockImplementation(async () => '4567');
|
||||
|
||||
await expect(
|
||||
getProjectIdAsync(sessionManager, { name: 'test', slug: 'test' }, { nonInteractive: false })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
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;
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import * as PackageManagerUtils from '@expo/package-manager';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import pkgDir from 'pkg-dir';
|
||||
import semver from 'semver';
|
||||
|
||||
import { learnMore } from '../../../log';
|
||||
import { easCliVersion } from '../../../utils/easCli';
|
||||
import { getVcsClient, setVcsClient } from '../../../vcs';
|
||||
import GitClient from '../../../vcs/clients/git';
|
||||
|
||||
async function applyCliConfigAsync(projectDir: string): Promise<void> {
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
);
|
||||
}
|
||||
if (config?.requireCommit) {
|
||||
setVcsClient(new GitClient());
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise<void> {
|
||||
let printCliVersionWarning = false;
|
||||
|
||||
const consoleWarn = (msg?: string): void => {
|
||||
if (msg) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(chalk.yellow(msg));
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn();
|
||||
}
|
||||
};
|
||||
|
||||
if (await isEasCliInDependenciesAsync(projectDir)) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||
}
|
||||
|
||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||
}
|
||||
|
||||
if (printCliVersionWarning) {
|
||||
consoleWarn(
|
||||
`It's recommended to use the ${chalk.bold(
|
||||
'"cli.version"'
|
||||
)} field in eas.json to enforce the ${chalk.bold('eas-cli')} version for your project.`
|
||||
);
|
||||
consoleWarn(
|
||||
learnMore('https://github.com/expo/eas-cli#enforcing-eas-cli-version-for-your-project')
|
||||
);
|
||||
consoleWarn();
|
||||
}
|
||||
}
|
||||
|
||||
async function isEasCliInDependenciesAsync(dir: string): Promise<boolean> {
|
||||
const packageJsonPath = path.join(dir, 'package.json');
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
return (
|
||||
packageJson?.dependencies?.['eas-cli'] !== undefined ||
|
||||
packageJson?.devDependencies?.['eas-cli'] !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the project root directory.
|
||||
*
|
||||
* @deprecated Should not be used outside of context functions.
|
||||
*/
|
||||
export async function findProjectRootAsync({
|
||||
cwd,
|
||||
defaultToProcessCwd = false,
|
||||
}: {
|
||||
cwd?: string;
|
||||
defaultToProcessCwd?: boolean;
|
||||
} = {}): Promise<string> {
|
||||
const projectRootDir = await pkgDir(cwd);
|
||||
if (!projectRootDir) {
|
||||
if (!defaultToProcessCwd) {
|
||||
throw new Error('Run this command inside a project directory.');
|
||||
} else {
|
||||
return process.cwd();
|
||||
}
|
||||
} else {
|
||||
let vcsRoot;
|
||||
try {
|
||||
vcsRoot = path.normalize(await getVcsClient().getRootPathAsync());
|
||||
} catch {}
|
||||
if (vcsRoot && vcsRoot.startsWith(projectRootDir) && vcsRoot !== projectRootDir) {
|
||||
throw new Error(
|
||||
`package.json is outside of the current git repository (project root: ${projectRootDir}, git root: ${vcsRoot}.`
|
||||
);
|
||||
}
|
||||
return projectRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the project root directory and ensure some constraints about the project setup
|
||||
* like CLI version and dependencies.
|
||||
* @returns the project root directory
|
||||
*
|
||||
* @deprecated Should not be used outside of context functions.
|
||||
*/
|
||||
export async function findProjectDirAndVerifyProjectSetupAsync(): Promise<string> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await applyCliConfigAsync(projectDir);
|
||||
await ensureEasCliIsNotInDependenciesAsync(projectDir);
|
||||
return projectDir;
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { getProjectConfigDescription, modifyConfigAsync } from '@expo/config';
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
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 { createGraphqlClient } from './createGraphqlClient';
|
||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||
|
||||
/**
|
||||
* Save an EAS project ID to the appropriate field in the app config.
|
||||
*
|
||||
* @deprecated Should not be used outside of context functions except in the init command.
|
||||
*/
|
||||
export async function saveProjectIdToAppConfigAsync(
|
||||
projectDir: string,
|
||||
projectId: string,
|
||||
options: { env?: Env } = {}
|
||||
): Promise<void> {
|
||||
const exp = getExpoConfig(projectDir, options);
|
||||
const result = await modifyConfigAsync(projectDir, {
|
||||
extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
|
||||
});
|
||||
|
||||
switch (result.type) {
|
||||
case 'success':
|
||||
break;
|
||||
case 'warn': {
|
||||
Log.warn();
|
||||
Log.warn(
|
||||
`Warning: Your project uses dynamic app configuration, and the EAS project ID can't automatically be added to it.`
|
||||
);
|
||||
Log.warn(
|
||||
chalk.dim(
|
||||
'https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs'
|
||||
)
|
||||
);
|
||||
Log.warn();
|
||||
Log.warn(
|
||||
`To complete the setup process, set "${chalk.bold(
|
||||
'extra.eas.projectId'
|
||||
)}" in your ${chalk.bold(getProjectConfigDescription(projectDir))}:`
|
||||
);
|
||||
Log.warn();
|
||||
Log.warn(chalk.bold(JSON.stringify({ expo: { extra: { eas: { projectId } } } }, null, 2)));
|
||||
Log.warn();
|
||||
throw new Error(result.message);
|
||||
}
|
||||
case 'fail':
|
||||
throw new Error(result.message);
|
||||
default:
|
||||
throw new Error('Unexpected result type from modifyConfigAsync');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the EAS project ID from the app config. If the project ID is not set in the config,
|
||||
* use the owner/slug to identify an EAS project on the server (asking for confirmation first),
|
||||
* and attempt to save the EAS project ID to the appropriate field in the app config. If unable to
|
||||
* save to the app config, throw an error.
|
||||
*
|
||||
* @returns the EAS project ID
|
||||
*
|
||||
* @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(graphqlClient, localProjectId);
|
||||
if (exp.owner && exp.owner !== appForProjectId.ownerAccount.name) {
|
||||
throw new Error(
|
||||
`Project config: Project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.ownerAccount.name
|
||||
}) is not owned by owner specified in the "owner" field (${exp.owner}). ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (exp.slug && exp.slug !== appForProjectId.slug) {
|
||||
throw new Error(
|
||||
`Project config: Slug for project identified by "extra.eas.projectId" (${
|
||||
appForProjectId.slug
|
||||
}) does not match the "slug" field (${exp.slug}). ${learnMore(
|
||||
'https://expo.fyi/eas-project-id'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return localProjectId;
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
if (!projectDir) {
|
||||
throw new Error('This command must be run inside a project directory.');
|
||||
}
|
||||
|
||||
Log.warn('EAS project not configured.');
|
||||
|
||||
const getAccountNameForEASProjectSync = (exp: ExpoConfig, user: Actor): string => {
|
||||
if (exp.owner) {
|
||||
return exp.owner;
|
||||
}
|
||||
switch (user.__typename) {
|
||||
case 'User':
|
||||
return user.username;
|
||||
case 'Robot':
|
||||
throw new Error(
|
||||
'The "owner" manifest property is required when using robot users. See: https://docs.expo.dev/versions/latest/config/app/#owner'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const projectId = await fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||
projectName: exp.slug,
|
||||
privacy: toAppPrivacy(exp.privacy),
|
||||
},
|
||||
{
|
||||
nonInteractive: options.nonInteractive,
|
||||
},
|
||||
actor
|
||||
);
|
||||
|
||||
const spinner = ora(`Linking local project to EAS project ${projectId}`).start();
|
||||
try {
|
||||
await saveProjectIdToAppConfigAsync(projectDir, projectId, options);
|
||||
spinner.succeed(`Linked local project to EAS project ${projectId}`);
|
||||
} catch (e: any) {
|
||||
spinner.fail();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return projectId;
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { showLoginPromptAsync } from '../../user/actions';
|
||||
|
||||
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,
|
||||
};
|
||||
protected override mustBeRunInsideProject = false;
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { sessionManager } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
|
||||
await sessionManager.showLoginPromptAsync();
|
||||
await showLoginPromptAsync();
|
||||
Log.log('Logged in');
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
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,
|
||||
};
|
||||
protected override mustBeRunInsideProject = false;
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { sessionManager } = await this.getContextAsync(AccountLogout, { nonInteractive: false });
|
||||
await sessionManager.logoutAsync();
|
||||
await logoutAsync();
|
||||
Log.log('Logged out');
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,19 @@ import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { getActorDisplayName } from '../../user/User';
|
||||
import { getActorDisplayName, getUserAsync } 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,
|
||||
};
|
||||
protected override mustBeRunInsideProject = false;
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
maybeLoggedIn: { actor },
|
||||
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
||||
if (actor) {
|
||||
Log.log(chalk.green(getActorDisplayName(actor)));
|
||||
const user = await getUserAsync();
|
||||
if (user) {
|
||||
Log.log(chalk.green(getActorDisplayName(user)));
|
||||
} else {
|
||||
Log.warn('Not logged in');
|
||||
process.exit(1);
|
||||
|
||||
@@ -7,6 +7,8 @@ export default class AnalyticsView extends EasCommand {
|
||||
|
||||
static override args = [{ name: 'STATUS', options: ['on', 'off'] }];
|
||||
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { STATUS: status } = (await this.parse(AnalyticsView)).args;
|
||||
if (status) {
|
||||
|
||||
@@ -3,23 +3,27 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
CreateUpdateBranchForAppMutation,
|
||||
CreateUpdateBranchForAppMutationVariables,
|
||||
UpdateBranch,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
export async function createUpdateBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
export async function createUpdateBranchOnAppAsync({
|
||||
appId,
|
||||
name,
|
||||
}: CreateUpdateBranchForAppMutationVariables): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
const result = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
||||
@@ -62,28 +66,19 @@ export default class BranchCreate extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name },
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(BranchCreate);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
if (!name) {
|
||||
const validationMessage = 'Branch name may not be empty.';
|
||||
@@ -99,7 +94,7 @@ export default class BranchCreate extends EasCommand {
|
||||
}));
|
||||
}
|
||||
|
||||
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, { appId: projectId, name });
|
||||
const newBranch = await createUpdateBranchOnAppAsync({ appId: projectId, name });
|
||||
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput(newBranch);
|
||||
|
||||
@@ -3,10 +3,9 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
DeleteUpdateBranchMutation,
|
||||
DeleteUpdateBranchMutationVariables,
|
||||
@@ -15,14 +14,19 @@ import {
|
||||
GetBranchInfoQueryVariables,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
async function getBranchInfoAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name }: GetBranchInfoQueryVariables
|
||||
): Promise<GetBranchInfoQuery> {
|
||||
async function getBranchInfoAsync({
|
||||
appId,
|
||||
name,
|
||||
}: GetBranchInfoQueryVariables): Promise<GetBranchInfoQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetBranchInfoQuery, GetBranchInfoQueryVariables>(
|
||||
@@ -50,10 +54,9 @@ async function getBranchInfoAsync(
|
||||
return data;
|
||||
}
|
||||
|
||||
async function deleteBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ branchId }: DeleteUpdateBranchMutationVariables
|
||||
): Promise<DeleteUpdateBranchResult> {
|
||||
async function deleteBranchOnAppAsync({
|
||||
branchId,
|
||||
}: DeleteUpdateBranchMutationVariables): Promise<DeleteUpdateBranchResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteUpdateBranchMutation, DeleteUpdateBranchMutationVariables>(
|
||||
@@ -78,11 +81,6 @@ async function deleteBranchOnAppAsync(
|
||||
export default class BranchDelete extends EasCommand {
|
||||
static override description = 'delete a branch';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override args = [
|
||||
{
|
||||
name: 'name',
|
||||
@@ -100,25 +98,23 @@ export default class BranchDelete extends EasCommand {
|
||||
args: { name: branchName },
|
||||
flags,
|
||||
} = await this.parse(BranchDelete);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchDelete, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
if (!branchName) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
if (nonInteractive) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
|
||||
({ name: branchName } = await selectBranchOnAppAsync({
|
||||
projectId,
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
promptTitle: 'Which branch would you like to delete?',
|
||||
@@ -126,7 +122,7 @@ export default class BranchDelete extends EasCommand {
|
||||
}));
|
||||
}
|
||||
|
||||
const data = await getBranchInfoAsync(graphqlClient, { appId: projectId, name: branchName });
|
||||
const data = await getBranchInfoAsync({ appId: projectId, name: branchName });
|
||||
const branchId = data.app?.byId.updateBranchByName?.id;
|
||||
if (!branchId) {
|
||||
throw new Error(`Could not find branch ${branchName} on ${projectDisplayName}`);
|
||||
@@ -146,7 +142,7 @@ export default class BranchDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const deletionResult = await deleteBranchOnAppAsync(graphqlClient, {
|
||||
const deletionResult = await deleteBranchOnAppAsync({
|
||||
branchId,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { listAndRenderBranchesOnAppAsync } from '../../branch/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class BranchList extends EasCommand {
|
||||
@@ -12,25 +14,19 @@ export default class BranchList extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
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'],
|
||||
});
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
|
||||
if (paginatedQueryOptions.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
await listAndRenderBranchesOnAppAsync(graphqlClient, { projectId, paginatedQueryOptions });
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, {
|
||||
nonInteractive: flags['non-interactive'],
|
||||
});
|
||||
await listAndRenderBranchesOnAppAsync({ projectId, paginatedQueryOptions });
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
EditUpdateBranchInput,
|
||||
EditUpdateBranchMutation,
|
||||
@@ -13,14 +12,20 @@ import {
|
||||
UpdateBranch,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
async function renameUpdateBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name, newName }: EditUpdateBranchInput
|
||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
async function renameUpdateBranchOnAppAsync({
|
||||
appId,
|
||||
name,
|
||||
newName,
|
||||
}: EditUpdateBranchInput): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<EditUpdateBranchMutation, EditUpdateBranchMutationVariables>(
|
||||
@@ -62,26 +67,18 @@ export default class BranchRename extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(BranchRename);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchRename, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
if (!currentName) {
|
||||
const validationMessage = 'current name may not be empty.';
|
||||
@@ -109,7 +106,7 @@ export default class BranchRename extends EasCommand {
|
||||
}));
|
||||
}
|
||||
|
||||
const editedBranch = await renameUpdateBranchOnAppAsync(graphqlClient, {
|
||||
const editedBranch = await renameUpdateBranchOnAppAsync({
|
||||
appId: projectId,
|
||||
name: currentName!,
|
||||
newName: newName!,
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
getLimitFlagWithCustomValues,
|
||||
getPaginatedQueryOptions,
|
||||
} from '../../commandUtils/pagination';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { listAndRenderUpdateGroupsOnBranchAsync } from '../../update/queries';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
@@ -26,35 +28,28 @@ export default class BranchView extends EasCommand {
|
||||
limit: getLimitFlagWithCustomValues({ defaultTo: 25, limit: 50 }),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name: branchName },
|
||||
flags,
|
||||
} = await this.parse(BranchView);
|
||||
const { 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchView, {
|
||||
nonInteractive,
|
||||
});
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
|
||||
if (paginatedQueryOptions.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
if (!branchName) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Branch name may not be empty.');
|
||||
}
|
||||
|
||||
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
|
||||
({ name: branchName } = await selectBranchOnAppAsync({
|
||||
projectId,
|
||||
promptTitle: 'Which branch would you like to view?',
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
@@ -67,7 +62,7 @@ export default class BranchView extends EasCommand {
|
||||
}));
|
||||
}
|
||||
|
||||
await listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
|
||||
await listAndRenderUpdateGroupsOnBranchAsync({
|
||||
projectId,
|
||||
branchName,
|
||||
paginatedQueryOptions,
|
||||
|
||||
@@ -2,9 +2,8 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
Build,
|
||||
BuildStatus,
|
||||
@@ -15,13 +14,15 @@ import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { appPlatformEmojis } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { confirmAsync, selectAsync } from '../../prompts';
|
||||
|
||||
async function cancelBuildAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
buildId: string
|
||||
): Promise<Pick<Build, 'id' | 'status'>> {
|
||||
async function cancelBuildAsync(buildId: string): Promise<Pick<Build, 'id' | 'status'>> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CancelBuildMutation, CancelBuildMutationVariables>(
|
||||
@@ -60,7 +61,6 @@ function formatUnfinishedBuild(
|
||||
}
|
||||
|
||||
export async function selectBuildToCancelAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
projectDisplayName: string
|
||||
): Promise<string | null> {
|
||||
@@ -69,19 +69,19 @@ export async function selectBuildToCancelAsync(
|
||||
let builds;
|
||||
try {
|
||||
const [newBuilds, inQueueBuilds, inProgressBuilds] = await Promise.all([
|
||||
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
filter: { status: BuildStatus.New },
|
||||
}),
|
||||
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
filter: { status: BuildStatus.InQueue },
|
||||
}),
|
||||
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
@@ -116,12 +116,9 @@ export async function selectBuildToCancelAsync(
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureBuildExistsAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
buildId: string
|
||||
): Promise<void> {
|
||||
async function ensureBuildExistsAsync(buildId: string): Promise<void> {
|
||||
try {
|
||||
await BuildQuery.byIdAsync(graphqlClient, buildId);
|
||||
await BuildQuery.byIdAsync(buildId);
|
||||
} catch {
|
||||
throw new Error(`Couldn't find a build matching the id ${buildId}`);
|
||||
}
|
||||
@@ -136,27 +133,19 @@ export default class BuildCancel extends EasCommand {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { BUILD_ID: buildIdFromArg },
|
||||
flags: { 'non-interactive': nonInteractive },
|
||||
} = await this.parse(BuildCancel);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildCancel, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
if (buildIdFromArg) {
|
||||
await ensureBuildExistsAsync(graphqlClient, buildIdFromArg);
|
||||
await ensureBuildExistsAsync(buildIdFromArg);
|
||||
}
|
||||
|
||||
let buildId: string | null = buildIdFromArg;
|
||||
@@ -165,7 +154,7 @@ export default class BuildCancel extends EasCommand {
|
||||
throw new Error('BUILD_ID must not be empty in non-interactive mode');
|
||||
}
|
||||
|
||||
buildId = await selectBuildToCancelAsync(graphqlClient, projectId, displayName);
|
||||
buildId = await selectBuildToCancelAsync(projectId, displayName);
|
||||
if (!buildId) {
|
||||
return;
|
||||
}
|
||||
@@ -173,7 +162,7 @@ export default class BuildCancel extends EasCommand {
|
||||
|
||||
const spinner = ora().start('Canceling the build…');
|
||||
try {
|
||||
const { status } = await cancelBuildAsync(graphqlClient, buildId);
|
||||
const { status } = await cancelBuildAsync(buildId);
|
||||
if (status === BuildStatus.Canceled) {
|
||||
spinner.succeed('Build canceled');
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,8 @@ import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { isExpoUpdatesInstalled } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, isExpoUpdatesInstalled } from '../../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from '../../update/android/UpdatesModule';
|
||||
@@ -25,19 +26,8 @@ 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,
|
||||
});
|
||||
|
||||
Log.log(
|
||||
'💡 The following process will configure your iOS and/or Android project to be compatible with EAS Build. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||
@@ -45,6 +35,7 @@ export default class BuildConfigure extends EasCommand {
|
||||
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const expoUpdatesIsInstalled = isExpoUpdatesInstalled(projectDir);
|
||||
|
||||
const platform =
|
||||
@@ -64,17 +55,19 @@ export default class BuildConfigure extends EasCommand {
|
||||
|
||||
// configure expo-updates
|
||||
if (expoUpdatesIsInstalled) {
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncAndroidUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
|
||||
await syncAndroidUpdatesConfigurationAsync(projectDir, exp, { nonInteractive: false });
|
||||
}
|
||||
}
|
||||
|
||||
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncIosUpdatesConfigurationAsync(graphqlClient, projectDir, exp, projectId);
|
||||
await syncIosUpdatesConfigurationAsync(projectDir, exp, { nonInteractive: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import figures from 'figures';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
@@ -13,6 +12,7 @@ import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { StatuspageServiceName } from '../../graphql/generated';
|
||||
import Log, { link } from '../../log';
|
||||
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import uniq from '../../utils/expodash/uniq';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
@@ -96,12 +96,6 @@ export default class Build extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(Build);
|
||||
|
||||
@@ -110,19 +104,12 @@ export default class Build extends EasCommand {
|
||||
}
|
||||
const flags = this.sanitizeFlags(rawFlags);
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicProjectConfigAsync,
|
||||
projectDir,
|
||||
} = await this.getContextAsync(Build, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
if (!flags.localBuildOptions.enable) {
|
||||
await maybeWarnAboutEasOutagesAsync(
|
||||
graphqlClient,
|
||||
flags.autoSubmit
|
||||
? [StatuspageServiceName.EasBuild, StatuspageServiceName.EasSubmit]
|
||||
: [StatuspageServiceName.EasBuild]
|
||||
@@ -131,13 +118,7 @@ export default class Build extends EasCommand {
|
||||
|
||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||
|
||||
await runBuildAndSubmitAsync(
|
||||
graphqlClient,
|
||||
projectDir,
|
||||
flagsWithPlatform,
|
||||
actor,
|
||||
getDynamicProjectConfigAsync
|
||||
);
|
||||
await runBuildAndSubmitAsync(projectDir, flagsWithPlatform);
|
||||
}
|
||||
|
||||
private sanitizeFlags(
|
||||
@@ -153,17 +134,6 @@ export default class Build extends EasCommand {
|
||||
if (flags.json && !nonInteractive) {
|
||||
Errors.error('--json is allowed only when building in non-interactive mode', { exit: 1 });
|
||||
}
|
||||
if (
|
||||
flags['resource-class'] === UserInputResourceClass.M1_EXPERIMENTAL &&
|
||||
flags.platform !== Platform.IOS
|
||||
) {
|
||||
Errors.error(
|
||||
`Resource class ${UserInputResourceClass.M1_EXPERIMENTAL} is only available for iOS builds`,
|
||||
{
|
||||
exit: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const requestedPlatform =
|
||||
flags.platform &&
|
||||
@@ -246,9 +216,6 @@ async function handleDeprecatedEasJsonAsync(
|
||||
projectDir: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
if (!(await fs.pathExists(EasJsonAccessor.formatEasJsonPath(projectDir)))) {
|
||||
return;
|
||||
}
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
const profileNames = await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor);
|
||||
const platformAndProfileNames: [Platform, string][] = profileNames.flatMap(profileName => [
|
||||
|
||||
@@ -9,6 +9,7 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { getTmpDirectory } from '../../utils/paths';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
@@ -61,22 +62,8 @@ export default class BuildInspect extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildInspect);
|
||||
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());
|
||||
|
||||
@@ -94,30 +81,25 @@ export default class BuildInspect extends EasCommand {
|
||||
await vcs.makeShallowCopyAsync(tmpWorkingdir);
|
||||
await this.copyToOutputDirAsync(tmpWorkingdir, outputDirectory);
|
||||
} else {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
try {
|
||||
await runBuildAndSubmitAsync(
|
||||
graphqlClient,
|
||||
projectDir,
|
||||
{
|
||||
nonInteractive: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
requestedPlatform: flags.platform,
|
||||
profile: flags.profile,
|
||||
localBuildOptions: {
|
||||
enable: true,
|
||||
...(flags.stage === InspectStage.PRE_BUILD ? { skipNativeBuild: true } : {}),
|
||||
...(flags.stage === InspectStage.POST_BUILD ? { skipCleanup: true } : {}),
|
||||
verbose: flags.verbose,
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
await runBuildAndSubmitAsync(projectDir, {
|
||||
nonInteractive: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
requestedPlatform: flags.platform,
|
||||
profile: flags.profile,
|
||||
localBuildOptions: {
|
||||
enable: true,
|
||||
...(flags.stage === InspectStage.PRE_BUILD ? { skipNativeBuild: true } : {}),
|
||||
...(flags.stage === InspectStage.POST_BUILD ? { skipCleanup: true } : {}),
|
||||
verbose: flags.verbose,
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
actor,
|
||||
getDynamicProjectConfigAsync
|
||||
);
|
||||
});
|
||||
if (!flags.verbose) {
|
||||
Log.log(chalk.green('Build successful'));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
BuildStatus as GraphQLBuildStatus,
|
||||
} from '../../graphql/generated';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class BuildList extends EasCommand {
|
||||
@@ -55,11 +60,6 @@ export default class BuildList extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildList);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
@@ -70,12 +70,6 @@ export default class BuildList extends EasCommand {
|
||||
distribution: buildDistribution,
|
||||
'non-interactive': nonInteractive,
|
||||
} = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildList, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
@@ -83,9 +77,13 @@ export default class BuildList extends EasCommand {
|
||||
const platform = toAppPlatform(requestedPlatform);
|
||||
const graphqlBuildStatus = toGraphQLBuildStatus(buildStatus);
|
||||
const graphqlBuildDistribution = toGraphQLBuildDistribution(buildDistribution);
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
await listAndRenderBuildsOnAppAsync(graphqlClient, {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
await listAndRenderBuildsOnAppAsync({
|
||||
projectId,
|
||||
projectDisplayName: displayName,
|
||||
filter: {
|
||||
|
||||
@@ -12,8 +12,13 @@ import Log from '../../../log';
|
||||
import { selectPlatformAsync } from '../../../platform';
|
||||
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
|
||||
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
||||
import { getExpoConfig } from '../../../project/expoConfig';
|
||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../../project/projectUtils';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../../project/projectUtils';
|
||||
import {
|
||||
ensureVersionSourceIsRemoteAsync,
|
||||
getBuildVersionName,
|
||||
@@ -37,21 +42,10 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildVersionSetView);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicProjectConfigAsync,
|
||||
projectDir,
|
||||
} = await this.getContextAsync(BuildVersionSetView, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
const platform = await selectPlatformAsync(flags.platform);
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
@@ -62,8 +56,11 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
flags.profile ?? undefined
|
||||
);
|
||||
|
||||
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: profile.env });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const exp = getExpoConfig(projectDir, { env: profile.env });
|
||||
|
||||
// this command is always interactive (see prompt below)
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, platform);
|
||||
|
||||
@@ -71,11 +68,9 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
projectDir,
|
||||
exp,
|
||||
profile,
|
||||
platform,
|
||||
actor
|
||||
platform
|
||||
);
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
projectId,
|
||||
toAppPlatform(platform),
|
||||
applicationIdentifier
|
||||
@@ -106,7 +101,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(graphqlClient, {
|
||||
await AppVersionMutation.createAppVersionAsync({
|
||||
appId: projectId,
|
||||
platform: toAppPlatform(platform),
|
||||
applicationIdentifier,
|
||||
|
||||
@@ -18,9 +18,11 @@ import {
|
||||
import { getAppBuildGradleAsync } from '../../../project/android/gradleUtils';
|
||||
import { VERSION_CODE_REQUIREMENTS, isValidVersionCode } from '../../../project/android/versions';
|
||||
import { getApplicationIdentifierAsync } from '../../../project/applicationIdentifier';
|
||||
import { getExpoConfig } from '../../../project/expoConfig';
|
||||
import { resolveXcodeBuildContextAsync } from '../../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../../project/ios/target';
|
||||
import { BUILD_NUMBER_REQUIREMENTS, isValidBuildNumber } from '../../../project/ios/versions';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../../project/projectUtils';
|
||||
import {
|
||||
ensureVersionSourceIsRemoteAsync,
|
||||
getBuildVersionName,
|
||||
@@ -54,21 +56,9 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildVersionSyncView);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicProjectConfigAsync,
|
||||
projectDir,
|
||||
} = await this.getContextAsync(BuildVersionSyncView, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
@@ -82,9 +72,10 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
profileName: flags.profile ?? undefined,
|
||||
});
|
||||
for (const profileInfo of buildProfiles) {
|
||||
const { exp, projectId } = await getDynamicProjectConfigAsync({
|
||||
env: profileInfo.profile.env,
|
||||
});
|
||||
const exp = getExpoConfig(projectDir, { env: profileInfo.profile.env });
|
||||
|
||||
// this command is always non-interactive
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
|
||||
validateAppConfigForRemoteVersionSource(exp, profileInfo.platform);
|
||||
const platformDisplayName = appPlatformDisplayNames[toAppPlatform(profileInfo.platform)];
|
||||
@@ -93,11 +84,9 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
projectDir,
|
||||
exp,
|
||||
profileInfo.profile,
|
||||
profileInfo.platform,
|
||||
actor
|
||||
profileInfo.platform
|
||||
);
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
projectId,
|
||||
toAppPlatform(profileInfo.platform),
|
||||
applicationIdentifier
|
||||
|
||||
@@ -5,7 +5,12 @@ import { BuildFragment } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
export default class BuildView extends EasCommand {
|
||||
@@ -17,27 +22,21 @@ export default class BuildView extends EasCommand {
|
||||
...EasJsonOnlyFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { BUILD_ID: buildId },
|
||||
flags,
|
||||
} = await this.parse(BuildView);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildView, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
if (flags.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
// this command is always non-interactive
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
const spinner = ora().start('Fetching the build…');
|
||||
|
||||
@@ -45,9 +44,9 @@ export default class BuildView extends EasCommand {
|
||||
let build: BuildFragment;
|
||||
|
||||
if (buildId) {
|
||||
build = await BuildQuery.byIdAsync(graphqlClient, buildId);
|
||||
build = await BuildQuery.byIdAsync(buildId);
|
||||
} else {
|
||||
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
const builds = await BuildQuery.viewBuildsOnAppAsync({
|
||||
appId: projectId,
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
|
||||
@@ -3,33 +3,34 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
CreateUpdateChannelOnAppMutation,
|
||||
CreateUpdateChannelOnAppMutationVariables,
|
||||
} from '../../graphql/generated';
|
||||
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { createUpdateBranchOnAppAsync } from '../branch/create';
|
||||
|
||||
export async function createUpdateChannelOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
}: {
|
||||
appId: string;
|
||||
channelName: string;
|
||||
branchId: string;
|
||||
}
|
||||
): Promise<CreateUpdateChannelOnAppMutation> {
|
||||
export async function createUpdateChannelOnAppAsync({
|
||||
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' }],
|
||||
@@ -74,26 +75,19 @@ export default class ChannelCreate extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name: channelName },
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ChannelCreate);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
if (!channelName) {
|
||||
const validationMessage = 'Channel name may not be empty.';
|
||||
if (nonInteractive) {
|
||||
@@ -111,7 +105,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
let branchMessage: string;
|
||||
|
||||
try {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
const branch = await BranchQuery.getBranchByNameAsync({
|
||||
appId: projectId,
|
||||
name: channelName,
|
||||
});
|
||||
@@ -119,7 +113,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(graphqlClient, {
|
||||
const newBranch = await createUpdateBranchOnAppAsync({
|
||||
appId: projectId,
|
||||
name: channelName,
|
||||
});
|
||||
@@ -132,7 +126,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
|
||||
const {
|
||||
updateChannel: { createUpdateChannelForApp: newChannel },
|
||||
} = await createUpdateChannelOnAppAsync(graphqlClient, {
|
||||
} = await createUpdateChannelOnAppAsync({
|
||||
appId: projectId,
|
||||
channelName,
|
||||
branchId,
|
||||
@@ -150,7 +144,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(
|
||||
`Created a new channel on project ${chalk.bold(
|
||||
await getDisplayNameForProjectIdAsync(graphqlClient, projectId)
|
||||
await getDisplayNameForProjectIdAsync(projectId)
|
||||
)}`
|
||||
);
|
||||
Log.log(
|
||||
|
||||
@@ -2,9 +2,8 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
DeleteUpdateChannelMutation,
|
||||
DeleteUpdateChannelMutationVariables,
|
||||
@@ -12,6 +11,8 @@ import {
|
||||
} from '../../graphql/generated';
|
||||
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
@@ -30,29 +31,22 @@ export default class ChannelDelete extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { name: nameArg },
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ChannelDelete);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
let channelId, channelName;
|
||||
if (nameArg) {
|
||||
const { id, name } = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
const { id, name } = await ChannelQuery.viewUpdateChannelAsync({
|
||||
appId: projectId,
|
||||
channelName: nameArg,
|
||||
});
|
||||
@@ -62,7 +56,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(graphqlClient, {
|
||||
const { id, name } = await selectChannelOnAppAsync({
|
||||
projectId,
|
||||
selectionPromptTitle: 'Select a channel to delete',
|
||||
paginatedQueryOptions: {
|
||||
@@ -88,7 +82,7 @@ export default class ChannelDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const deletionResult = await deleteChannelOnAppAsync(graphqlClient, {
|
||||
const deletionResult = await deleteChannelOnAppAsync({
|
||||
channelId,
|
||||
});
|
||||
|
||||
@@ -100,10 +94,9 @@ export default class ChannelDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteChannelOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ channelId }: DeleteUpdateChannelMutationVariables
|
||||
): Promise<DeleteUpdateChannelResult> {
|
||||
async function deleteChannelOnAppAsync({
|
||||
channelId,
|
||||
}: DeleteUpdateChannelMutationVariables): Promise<DeleteUpdateChannelResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteUpdateChannelMutation, DeleteUpdateChannelMutationVariables>(
|
||||
|
||||
@@ -5,9 +5,8 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
UpdateChannelBranchMappingMutation,
|
||||
UpdateChannelBranchMappingMutationVariables,
|
||||
@@ -15,12 +14,16 @@ import {
|
||||
import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
export async function updateChannelBranchMappingAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ channelId, branchMapping }: UpdateChannelBranchMappingMutationVariables
|
||||
): Promise<UpdateChannelBranchMappingMutation['updateChannel']['editUpdateChannel']> {
|
||||
export async function updateChannelBranchMappingAsync({
|
||||
channelId,
|
||||
branchMapping,
|
||||
}: UpdateChannelBranchMappingMutationVariables): Promise<
|
||||
UpdateChannelBranchMappingMutation['updateChannel']['editUpdateChannel']
|
||||
> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<UpdateChannelBranchMappingMutation, UpdateChannelBranchMappingMutationVariables>(
|
||||
@@ -64,32 +67,22 @@ export default class ChannelEdit extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args,
|
||||
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ChannelEdit);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelEdit, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
const existingChannel = args.name
|
||||
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName: args.name,
|
||||
})
|
||||
: await selectChannelOnAppAsync(graphqlClient, {
|
||||
? await ChannelQuery.viewUpdateChannelAsync({ appId: projectId, channelName: args.name })
|
||||
: await selectChannelOnAppAsync({
|
||||
projectId,
|
||||
selectionPromptTitle: 'Select a channel to edit',
|
||||
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
|
||||
@@ -100,11 +93,11 @@ export default class ChannelEdit extends EasCommand {
|
||||
}
|
||||
|
||||
const branch = branchFlag
|
||||
? await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
? await BranchQuery.getBranchByNameAsync({
|
||||
appId: projectId,
|
||||
name: branchFlag,
|
||||
})
|
||||
: await selectBranchOnAppAsync(graphqlClient, {
|
||||
: await selectBranchOnAppAsync({
|
||||
projectId,
|
||||
promptTitle: `Which branch would you like ${existingChannel.name} to point at?`,
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
@@ -115,7 +108,7 @@ export default class ChannelEdit extends EasCommand {
|
||||
},
|
||||
});
|
||||
|
||||
const channel = await updateChannelBranchMappingAsync(graphqlClient, {
|
||||
const channel = await updateChannelBranchMappingAsync({
|
||||
channelId: existingChannel.id,
|
||||
// todo: move branch mapping logic to utility
|
||||
branchMapping: JSON.stringify({
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
getLimitFlagWithCustomValues,
|
||||
getPaginatedQueryOptions,
|
||||
} from '../../commandUtils/pagination';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class ChannelList extends EasCommand {
|
||||
@@ -17,26 +19,19 @@ export default class ChannelList extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(ChannelList);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelList, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
await listAndRenderChannelsOnAppAsync(graphqlClient, {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
await listAndRenderChannelsOnAppAsync({
|
||||
projectId,
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
|
||||
@@ -3,13 +3,17 @@ 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';
|
||||
import { ChannelQuery, UpdateChannelByNameObject } from '../../graphql/queries/ChannelQuery';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { updateChannelBranchMappingAsync } from './edit';
|
||||
@@ -55,28 +59,25 @@ function getRolloutInfo(channel: UpdateChannelByNameObject): {
|
||||
return { newBranch, oldBranch, currentPercent };
|
||||
}
|
||||
|
||||
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<{
|
||||
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<{
|
||||
newChannelInfo: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -84,7 +85,7 @@ async function startRolloutAsync(
|
||||
};
|
||||
logMessage: string;
|
||||
}> {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
const branch = await BranchQuery.getBranchByNameAsync({
|
||||
appId: projectId,
|
||||
name: branchName,
|
||||
});
|
||||
@@ -120,7 +121,7 @@ async function startRolloutAsync(
|
||||
currentBranchMapping.data[0],
|
||||
],
|
||||
};
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync({
|
||||
channelId: channel.id,
|
||||
branchMapping: JSON.stringify(newBranchMapping),
|
||||
});
|
||||
@@ -143,22 +144,19 @@ async function startRolloutAsync(
|
||||
return { newChannelInfo, logMessage };
|
||||
}
|
||||
|
||||
async function editRolloutAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
channelName,
|
||||
percent,
|
||||
nonInteractive,
|
||||
currentBranchMapping,
|
||||
channel,
|
||||
}: {
|
||||
channelName?: string;
|
||||
percent?: number;
|
||||
nonInteractive: boolean;
|
||||
currentBranchMapping: BranchMapping;
|
||||
channel: UpdateChannelByNameObject;
|
||||
}
|
||||
): Promise<{
|
||||
async function editRolloutAsync({
|
||||
channelName,
|
||||
percent,
|
||||
nonInteractive,
|
||||
currentBranchMapping,
|
||||
channel,
|
||||
}: {
|
||||
channelName?: string;
|
||||
percent?: number;
|
||||
nonInteractive: boolean;
|
||||
currentBranchMapping: BranchMapping;
|
||||
channel: UpdateChannelByNameObject;
|
||||
}): Promise<{
|
||||
newChannelInfo: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -185,7 +183,7 @@ async function editRolloutAsync(
|
||||
const newBranchMapping = { ...currentBranchMapping };
|
||||
newBranchMapping.data[0].branchMappingLogic.operand = percent / 100;
|
||||
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync({
|
||||
channelId: channel.id,
|
||||
branchMapping: JSON.stringify(newBranchMapping),
|
||||
});
|
||||
@@ -201,22 +199,19 @@ async function editRolloutAsync(
|
||||
return { newChannelInfo, logMessage };
|
||||
}
|
||||
|
||||
async function endRolloutAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
channelName,
|
||||
branchName,
|
||||
nonInteractive,
|
||||
projectId,
|
||||
channel,
|
||||
}: {
|
||||
channelName?: string;
|
||||
branchName?: string;
|
||||
nonInteractive: boolean;
|
||||
projectId: string;
|
||||
channel: UpdateChannelByNameObject;
|
||||
}
|
||||
): Promise<{
|
||||
async function endRolloutAsync({
|
||||
channelName,
|
||||
branchName,
|
||||
nonInteractive,
|
||||
projectId,
|
||||
channel,
|
||||
}: {
|
||||
channelName?: string;
|
||||
branchName?: string;
|
||||
nonInteractive: boolean;
|
||||
projectId: string;
|
||||
channel: UpdateChannelByNameObject;
|
||||
}): Promise<{
|
||||
newChannelInfo: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -229,7 +224,7 @@ async function endRolloutAsync(
|
||||
|
||||
let endOnNewBranch;
|
||||
if (branchName) {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
const branch = await BranchQuery.getBranchByNameAsync({
|
||||
appId: projectId,
|
||||
name: branchName,
|
||||
});
|
||||
@@ -280,7 +275,7 @@ async function endRolloutAsync(
|
||||
],
|
||||
};
|
||||
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync(graphqlClient, {
|
||||
const newChannelInfo = await updateChannelBranchMappingAsync({
|
||||
channelId: channel.id,
|
||||
branchMapping: JSON.stringify(newBranchMapping),
|
||||
});
|
||||
@@ -321,11 +316,6 @@ export default class ChannelRollout extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { channel: channelName },
|
||||
@@ -337,19 +327,16 @@ export default class ChannelRollout extends EasCommand {
|
||||
'non-interactive': nonInteractive,
|
||||
},
|
||||
} = await this.parse(ChannelRollout);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelRollout, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync({
|
||||
appId: projectId,
|
||||
channelName: channelName!,
|
||||
});
|
||||
@@ -394,7 +381,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
logMessage: string;
|
||||
};
|
||||
if (!isRollout) {
|
||||
rolloutMutationResult = await startRolloutAsync(graphqlClient, {
|
||||
rolloutMutationResult = await startRolloutAsync({
|
||||
channelName,
|
||||
branchName: branchName ?? (await promptForBranchNameAsync(channelName, nonInteractive)),
|
||||
percent,
|
||||
@@ -405,7 +392,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
channel,
|
||||
});
|
||||
} else if (endFlag) {
|
||||
rolloutMutationResult = await endRolloutAsync(graphqlClient, {
|
||||
rolloutMutationResult = await endRolloutAsync({
|
||||
channelName,
|
||||
branchName,
|
||||
nonInteractive,
|
||||
@@ -413,7 +400,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
channel,
|
||||
});
|
||||
} else {
|
||||
rolloutMutationResult = await editRolloutAsync(graphqlClient, {
|
||||
rolloutMutationResult = await editRolloutAsync({
|
||||
channelName,
|
||||
percent,
|
||||
nonInteractive,
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class ChannelView extends EasCommand {
|
||||
@@ -25,11 +27,6 @@ export default class ChannelView extends EasCommand {
|
||||
...EasPaginatedQueryFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { name: channelName },
|
||||
@@ -37,22 +34,20 @@ export default class ChannelView extends EasCommand {
|
||||
} = await this.parse(ChannelView);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelView, {
|
||||
nonInteractive,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
if (!channelName) {
|
||||
const validationMessage = 'A channel name is required to view a specific channel.';
|
||||
if (nonInteractive) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
const selectedUpdateChannel = await selectChannelOnAppAsync(graphqlClient, {
|
||||
const selectedUpdateChannel = await selectChannelOnAppAsync({
|
||||
projectId,
|
||||
selectionPromptTitle: 'Select a channel to view',
|
||||
paginatedQueryOptions: {
|
||||
@@ -65,7 +60,7 @@ export default class ChannelView extends EasCommand {
|
||||
assert(channelName, `A channel must be selected.`);
|
||||
}
|
||||
|
||||
await listAndRenderBranchesAndUpdatesOnChannelAsync(graphqlClient, {
|
||||
await listAndRenderBranchesAndUpdatesOnChannelAsync({
|
||||
projectId,
|
||||
channelName,
|
||||
paginatedQueryOptions,
|
||||
|
||||
@@ -8,13 +8,15 @@ import EasCommand from '../commandUtils/EasCommand';
|
||||
import { toAppPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log from '../log';
|
||||
import { appPlatformEmojis } from '../platform';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { selectAsync } from '../prompts';
|
||||
|
||||
export default class Config extends EasCommand {
|
||||
static override description = 'display project configuration (app.json + eas.json)';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum<Platform>({ char: 'p', options: [Platform.ANDROID, Platform.IOS] }),
|
||||
platform: Flags.enum({ char: 'p', options: ['android', 'ios'] }),
|
||||
profile: Flags.string({
|
||||
char: 'e',
|
||||
description:
|
||||
@@ -23,17 +25,16 @@ export default class Config extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(Config);
|
||||
const { platform: maybePlatform, profile: maybeProfile } = flags;
|
||||
const { getDynamicProjectConfigAsync, projectDir } = await this.getContextAsync(Config, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
const { platform: maybePlatform, profile: maybeProfile } = flags as {
|
||||
platform?: Platform;
|
||||
profile?: string;
|
||||
};
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
|
||||
const accessor = new EasJsonAccessor(projectDir);
|
||||
const profileName =
|
||||
@@ -61,10 +62,7 @@ export default class Config extends EasCommand {
|
||||
]));
|
||||
|
||||
const profile = await EasJsonUtils.getBuildProfileAsync(accessor, platform, profileName);
|
||||
const { exp: config } = await getDynamicProjectConfigAsync({
|
||||
env: profile.env,
|
||||
isPublicConfig: true,
|
||||
});
|
||||
const config = getExpoConfig(projectDir, { env: profile.env, isPublicConfig: true });
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold(getProjectConfigDescription(projectDir)));
|
||||
|
||||
@@ -10,25 +10,8 @@ export default class Credentials extends EasCommand {
|
||||
platform: Flags.enum({ char: 'p', options: ['android', 'ios'] }),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.OptionalProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(Credentials);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectConfig,
|
||||
} = await this.getContextAsync(Credentials, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
await new SelectPlatform(
|
||||
actor,
|
||||
graphqlClient,
|
||||
projectConfig ?? null,
|
||||
flags.platform
|
||||
).runAsync();
|
||||
await new SelectPlatform(flags.platform).runAsync();
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,16 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import AppStoreApi from '../../credentials/ios/appstore/AppStoreApi';
|
||||
import { createContextAsync } from '../../devices/context';
|
||||
import DeviceManager from '../../devices/manager';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
|
||||
export default class DeviceCreate extends EasCommand {
|
||||
static override description = 'register new Apple Devices to use for internal distribution';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.OptionalProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
// this command is interactive by design
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectConfig,
|
||||
} = await this.getContextAsync(DeviceCreate, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
const user = await ensureLoggedInAsync({ nonInteractive: false });
|
||||
|
||||
const ctx = await createContextAsync({
|
||||
appStore: new AppStoreApi(),
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
projectId: projectConfig?.projectId,
|
||||
});
|
||||
const ctx = await createContextAsync({ appStore: new AppStoreApi(), user });
|
||||
const manager = new DeviceManager(ctx);
|
||||
await manager.createAsync();
|
||||
}
|
||||
|
||||
@@ -3,164 +3,152 @@ 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 { chooseDevicesToDeleteAsync } from '../../credentials/ios/actions/DeviceUtils';
|
||||
import { AppleDeviceMutation } from '../../credentials/ios/api/graphql/mutations/AppleDeviceMutation';
|
||||
import {
|
||||
AppleDeviceQuery,
|
||||
AppleDeviceQueryResult,
|
||||
AppleDevicesByTeamIdentifierQueryResult,
|
||||
} from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
||||
import { AppleTeamQuery } from '../../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
||||
import { authenticateAsync, getRequestContext } from '../../credentials/ios/appstore/authenticate';
|
||||
import {
|
||||
selectAppleDeviceOnAppleTeamAsync,
|
||||
selectAppleTeamOnAccountAsync,
|
||||
} from '../../devices/queries';
|
||||
import formatDevice from '../../devices/utils/formatDevice';
|
||||
import { AppleDevice, Maybe } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
|
||||
export default class DeviceDelete extends EasCommand {
|
||||
static override description = 'remove a registered device from your account';
|
||||
|
||||
static override flags = {
|
||||
'apple-team-id': Flags.string({ description: 'The Apple team ID on which to find the device' }),
|
||||
udid: Flags.string({ description: 'The Apple device ID to disable' }),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
'apple-team-id': Flags.string(),
|
||||
udid: Flags.string({ multiple: true }),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(DeviceDelete);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
let { 'apple-team-id': appleTeamIdentifier, udid } = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(DeviceDelete, {
|
||||
nonInteractive: paginatedQueryOptions.nonInteractive,
|
||||
});
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
let appleTeamName;
|
||||
let {
|
||||
flags: { 'apple-team-id': appleTeamIdentifier, udid: udids },
|
||||
} = await this.parse(DeviceDelete);
|
||||
|
||||
if (paginatedQueryOptions.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
// this command is interactive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
if (!appleTeamIdentifier) {
|
||||
const appleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
|
||||
accountName: account.name,
|
||||
selectionPromptTitle: `What Apple team would you like to list devices for?`,
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
appleTeamIdentifier = appleTeam.appleTeamIdentifier;
|
||||
appleTeamName = appleTeam.appleTeamName;
|
||||
appleTeamIdentifier = await this.askForAppleTeamAsync(account.name);
|
||||
}
|
||||
|
||||
assert(appleTeamIdentifier, 'No team identifier is specified');
|
||||
|
||||
const chosenDevice = udid
|
||||
? await AppleDeviceQuery.getByDeviceIdentifierAsync(graphqlClient, account.name, udid)
|
||||
: await selectAppleDeviceOnAppleTeamAsync(graphqlClient, {
|
||||
accountName: account.name,
|
||||
appleTeamIdentifier,
|
||||
selectionPromptTitle: `Which device would you like to disable?`,
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
const appleDevicesResult = await this.getDevicesForTeamAsync(account.name, appleTeamIdentifier);
|
||||
|
||||
this.logChosenDevice(chosenDevice, appleTeamName, appleTeamIdentifier, paginatedQueryOptions);
|
||||
|
||||
if (!(await this.shouldRemoveDeviceFromExpoAsync(paginatedQueryOptions))) {
|
||||
if (!appleDevicesResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.removeDeviceFromExpoAsync(graphqlClient, chosenDevice);
|
||||
const { appleTeamName, appleDevices } = appleDevicesResult;
|
||||
|
||||
if (await this.shouldDisableDeviceOnAppleAsync(paginatedQueryOptions)) {
|
||||
await this.disableDeviceOnAppleAsync(chosenDevice, appleTeamIdentifier);
|
||||
}
|
||||
}
|
||||
const chosenDevices = await this.chooseDevicesToDeleteAsync(appleDevices, udids);
|
||||
|
||||
async shouldDisableDeviceOnAppleAsync({
|
||||
nonInteractive,
|
||||
}: PaginatedQueryOptions): Promise<boolean> {
|
||||
if (!nonInteractive) {
|
||||
if (chosenDevices.length === 0) {
|
||||
Log.newLine();
|
||||
return await toggleConfirmAsync({
|
||||
message: 'Do you want to disable this device on your Apple account as well?',
|
||||
});
|
||||
Log.warn('No devices were chosen to be removed.');
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
|
||||
this.logChosenDevices(chosenDevices, appleTeamName, appleTeamIdentifier);
|
||||
|
||||
const hasRemoved = await this.askAndRemoveFromExpoAsync(chosenDevices);
|
||||
|
||||
if (!hasRemoved) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.askAndDisableOnAppleAsync(chosenDevices, appleTeamIdentifier);
|
||||
}
|
||||
|
||||
async disableDeviceOnAppleAsync(
|
||||
device: AppleDevice | AppleDeviceQueryResult,
|
||||
async askAndDisableOnAppleAsync(
|
||||
chosenDevices: (AppleDevice | AppleDeviceQueryResult)[],
|
||||
appleTeamIdentifier: string
|
||||
): Promise<void> {
|
||||
Log.newLine();
|
||||
const deleteOnApple = await toggleConfirmAsync({
|
||||
message: 'Do you want to disable these devices on your Apple account as well?',
|
||||
});
|
||||
|
||||
if (!deleteOnApple) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = await authenticateAsync({ teamId: appleTeamIdentifier });
|
||||
const context = getRequestContext(ctx);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
const removeAppleSpinner = ora('Disabling device on Apple').start();
|
||||
const removeAppleSpinner = ora('Disabling devices on Apple').start();
|
||||
try {
|
||||
const appleValidatedDevices = await Device.getAsync(context);
|
||||
const appleValidatedDevice = appleValidatedDevices.find(d => d.id === device.id);
|
||||
if (appleValidatedDevice) {
|
||||
await appleValidatedDevice.updateAsync({ status: DeviceStatus.DISABLED });
|
||||
const chosenDeviceIdentifiers = chosenDevices.map(cd => cd.identifier);
|
||||
const allDevices = await Device.getAsync(context);
|
||||
const realDevices = allDevices.filter(d =>
|
||||
chosenDeviceIdentifiers.includes(d.attributes.udid)
|
||||
);
|
||||
|
||||
for (const device of realDevices) {
|
||||
await device.updateAsync({ status: DeviceStatus.DISABLED });
|
||||
}
|
||||
removeAppleSpinner.succeed('Disabled device on Apple');
|
||||
|
||||
removeAppleSpinner.succeed('Disabled devices on Apple');
|
||||
} catch (err) {
|
||||
removeAppleSpinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async shouldRemoveDeviceFromExpoAsync({
|
||||
nonInteractive,
|
||||
}: PaginatedQueryOptions): Promise<boolean> {
|
||||
if (!nonInteractive) {
|
||||
Log.warn(`You are about to remove the Apple device listed above from your Expo account.`);
|
||||
Log.newLine();
|
||||
async askAndRemoveFromExpoAsync(
|
||||
chosenDevices: (AppleDevice | AppleDeviceQueryResult)[]
|
||||
): Promise<boolean> {
|
||||
Log.warn(
|
||||
`You are about to remove the Apple device${
|
||||
chosenDevices.length > 1 ? 's' : ''
|
||||
} listed above from your Expo account.`
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
return await toggleConfirmAsync({
|
||||
message: 'Are you sure you wish to proceed?',
|
||||
});
|
||||
const confirmed = await toggleConfirmAsync({
|
||||
message: 'Are you sure you wish to proceed?',
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
const removalSpinner = ora(`Removing Apple devices on Expo`).start();
|
||||
try {
|
||||
for (const chosenDevice of chosenDevices) {
|
||||
await AppleDeviceMutation.deleteAppleDeviceAsync(chosenDevice.id);
|
||||
}
|
||||
removalSpinner.succeed('Removed Apple devices from Expo');
|
||||
} catch (err) {
|
||||
removalSpinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
async removeDeviceFromExpoAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
chosenDevice: AppleDevice | AppleDeviceQueryResult
|
||||
): Promise<void> {
|
||||
const removalSpinner = ora(`Removing Apple device on Expo`).start();
|
||||
try {
|
||||
await AppleDeviceMutation.deleteAppleDeviceAsync(graphqlClient, chosenDevice.id);
|
||||
removalSpinner.succeed('Removed Apple device from Expo');
|
||||
} catch (err) {
|
||||
removalSpinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
logChosenDevice(
|
||||
device: AppleDevice | AppleDeviceQueryResult,
|
||||
logChosenDevices(
|
||||
chosenDevices: (AppleDevice | AppleDeviceQueryResult)[],
|
||||
appleTeamName: Maybe<string> | undefined,
|
||||
appleTeamIdentifier: string,
|
||||
{ json }: PaginatedQueryOptions
|
||||
appleTeamIdentifier: string
|
||||
): void {
|
||||
if (json) {
|
||||
printJsonOnlyOutput(device);
|
||||
} else {
|
||||
Log.addNewLineIfNone();
|
||||
Log.addNewLineIfNone();
|
||||
chosenDevices.forEach(device => {
|
||||
Log.log(
|
||||
formatDevice(device, {
|
||||
appleTeamName,
|
||||
@@ -168,6 +156,98 @@ export default class DeviceDelete extends EasCommand {
|
||||
})
|
||||
);
|
||||
Log.newLine();
|
||||
});
|
||||
}
|
||||
|
||||
async chooseDevicesToDeleteAsync(
|
||||
appleDevices: AppleDeviceQueryResult[],
|
||||
udids: string[]
|
||||
): Promise<(AppleDevice | AppleDeviceQueryResult)[]> {
|
||||
let chosenDevices: (AppleDeviceQueryResult | AppleDevice)[] = [];
|
||||
Log.newLine();
|
||||
if (udids) {
|
||||
udids.forEach(udid => {
|
||||
const foundDevice = appleDevices.find(device => device.identifier === udid);
|
||||
if (foundDevice) {
|
||||
chosenDevices.push(foundDevice);
|
||||
} else {
|
||||
Log.warn(`No device found with UDID ${udid}.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (chosenDevices.length === 0) {
|
||||
Log.addNewLineIfNone();
|
||||
chosenDevices = await chooseDevicesToDeleteAsync(appleDevices);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
return chosenDevices;
|
||||
}
|
||||
|
||||
async getDevicesForTeamAsync(
|
||||
accountName: string,
|
||||
appleTeamIdentifier: string
|
||||
): Promise<AppleDevicesByTeamIdentifierQueryResult | undefined> {
|
||||
const devicesSpinner = ora().start('Fetching the list of devices for the team…');
|
||||
|
||||
try {
|
||||
const result = await AppleDeviceQuery.getAllForAppleTeamAsync(
|
||||
accountName,
|
||||
appleTeamIdentifier
|
||||
);
|
||||
|
||||
if (result?.appleDevices.length) {
|
||||
const { appleTeamName, appleDevices } = result;
|
||||
|
||||
devicesSpinner.succeed(
|
||||
`Found ${appleDevices.length} devices for team ${appleTeamName ?? appleTeamIdentifier}`
|
||||
);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
devicesSpinner.fail(`Couldn't find any devices for the team ${appleTeamIdentifier}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
devicesSpinner.fail(`Something went wrong and we couldn't fetch the device list`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async askForAppleTeamAsync(accountName: string): Promise<string | undefined> {
|
||||
const teamSpinner = ora().start('Fetching the list of teams for the project…');
|
||||
|
||||
try {
|
||||
const teams = await AppleTeamQuery.getAllForAccountAsync(accountName);
|
||||
|
||||
if (teams.length > 0) {
|
||||
teamSpinner.succeed('Fetched the list of teams for the project');
|
||||
|
||||
if (teams.length === 1) {
|
||||
return teams[0].appleTeamIdentifier;
|
||||
}
|
||||
|
||||
const result = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'appleTeamIdentifier',
|
||||
message: 'What Apple Team would you like to list devices for?',
|
||||
choices: teams.map(team => ({
|
||||
title: team.appleTeamName
|
||||
? `${team.appleTeamName} (ID: ${team.appleTeamIdentifier})`
|
||||
: team.appleTeamIdentifier,
|
||||
value: team.appleTeamIdentifier,
|
||||
})),
|
||||
});
|
||||
|
||||
return result.appleTeamIdentifier;
|
||||
} else {
|
||||
teamSpinner.fail(`Couldn't find any teams for the account ${accountName}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
teamSpinner.fail(`Something went wrong and we couldn't fetch the list of teams`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,105 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { EasPaginatedQueryFlags, getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/AppleDeviceQuery';
|
||||
import { AppleTeamQuery } from '../../credentials/ios/api/graphql/queries/AppleTeamQuery';
|
||||
import formatDevice from '../../devices/utils/formatDevice';
|
||||
import Log from '../../log';
|
||||
import { Ora, ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
listAndRenderAppleDevicesOnAppleTeamAsync,
|
||||
selectAppleTeamOnAccountAsync,
|
||||
} from '../../devices/queries';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
findProjectRootAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
|
||||
export default class BuildList extends EasCommand {
|
||||
static override description = 'list all registered devices for your account';
|
||||
|
||||
static override flags = {
|
||||
'apple-team-id': Flags.string(),
|
||||
...EasPaginatedQueryFlags,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BuildList);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildList, {
|
||||
nonInteractive: paginatedQueryOptions.nonInteractive,
|
||||
});
|
||||
let appleTeamIdentifier = flags['apple-team-id'];
|
||||
let appleTeamName;
|
||||
if (paginatedQueryOptions.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
let appleTeamIdentifier = (await this.parse(BuildList)).flags['apple-team-id'];
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
// this command is interactive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
let spinner: Ora;
|
||||
|
||||
// if they don't provide a team id, fetch devices on their account
|
||||
if (!appleTeamIdentifier) {
|
||||
const selectedAppleTeam = await selectAppleTeamOnAccountAsync(graphqlClient, {
|
||||
accountName: account.name,
|
||||
paginatedQueryOptions: {
|
||||
...paginatedQueryOptions,
|
||||
offset: 0,
|
||||
limit: undefined,
|
||||
},
|
||||
selectionPromptTitle: 'What Apple team would you like to list devices for?',
|
||||
});
|
||||
appleTeamIdentifier = selectedAppleTeam.appleTeamIdentifier;
|
||||
appleTeamName = selectedAppleTeam.appleTeamName;
|
||||
spinner = ora().start('Fetching the list of teams for the project…');
|
||||
|
||||
try {
|
||||
const teams = await AppleTeamQuery.getAllForAccountAsync(account.name);
|
||||
|
||||
if (teams.length > 0) {
|
||||
spinner.succeed();
|
||||
|
||||
if (teams.length === 1) {
|
||||
appleTeamIdentifier = teams[0].appleTeamIdentifier;
|
||||
} else {
|
||||
const result = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'appleTeamIdentifier',
|
||||
message: 'What Apple Team would you like to list devices for?',
|
||||
choices: teams.map(team => ({
|
||||
title: team.appleTeamName
|
||||
? `${team.appleTeamName} (ID: ${team.appleTeamIdentifier})`
|
||||
: team.appleTeamIdentifier,
|
||||
value: team.appleTeamIdentifier,
|
||||
})),
|
||||
});
|
||||
|
||||
appleTeamIdentifier = result.appleTeamIdentifier;
|
||||
}
|
||||
} else {
|
||||
spinner.fail(`Couldn't find any teams for the account ${account.name}`);
|
||||
}
|
||||
} catch (e) {
|
||||
spinner.fail(`Something went wrong and we couldn't fetch the list of teams`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
assert(appleTeamIdentifier, 'No team identifier is specified');
|
||||
await listAndRenderAppleDevicesOnAppleTeamAsync(graphqlClient, {
|
||||
accountName: account.name,
|
||||
appleTeam: {
|
||||
appleTeamIdentifier,
|
||||
appleTeamName,
|
||||
},
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
|
||||
spinner = ora().start('Fetching the list of devices for the team…');
|
||||
|
||||
try {
|
||||
const result = await AppleDeviceQuery.getAllForAppleTeamAsync(
|
||||
account.name,
|
||||
appleTeamIdentifier
|
||||
);
|
||||
|
||||
if (result?.appleDevices.length) {
|
||||
const { appleTeamName, appleDevices } = result;
|
||||
|
||||
spinner.succeed(
|
||||
`Found ${appleDevices.length} devices for team ${appleTeamName ?? appleTeamIdentifier}`
|
||||
);
|
||||
|
||||
const list = appleDevices
|
||||
.map(device =>
|
||||
formatDevice(device, { appleTeamName, appleTeamIdentifier: appleTeamIdentifier! })
|
||||
)
|
||||
.join(`\n\n${chalk.dim('———')}\n\n`);
|
||||
|
||||
Log.log(`\n${list}`);
|
||||
} else {
|
||||
spinner.fail(`Couldn't find any devices for the team ${appleTeamIdentifier}`);
|
||||
}
|
||||
} catch (e) {
|
||||
spinner.fail(`Something went wrong and we couldn't fetch the device list`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,18 @@ import { AppleDeviceQuery } from '../../credentials/ios/api/graphql/queries/Appl
|
||||
import formatDevice from '../../devices/utils/formatDevice';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
|
||||
export default class DeviceView extends EasCommand {
|
||||
static override description = 'view a device for your project';
|
||||
|
||||
static override args = [{ name: 'UDID' }];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { UDID } = (await this.parse(DeviceView)).args;
|
||||
|
||||
@@ -31,22 +31,18 @@ If you are not sure what is the UDID of the device you are looking for, run:
|
||||
);
|
||||
throw new Error('Device UDID is missing');
|
||||
}
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(DeviceView, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
// this command is non-interactive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
const spinner = ora().start(`Fetching device details for ${UDID}…`);
|
||||
|
||||
try {
|
||||
const device = await AppleDeviceQuery.getByDeviceIdentifierAsync(
|
||||
graphqlClient,
|
||||
account.name,
|
||||
UDID
|
||||
);
|
||||
const device = await AppleDeviceQuery.getByDeviceIdentifierAsync(account.name, UDID);
|
||||
|
||||
if (device) {
|
||||
spinner.succeed('Fetched device details');
|
||||
|
||||
@@ -3,21 +3,16 @@ import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export default class Diagnostics extends EasCommand {
|
||||
static override description = 'display environment info';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
protected override requiresAuthentication = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { projectDir } = await this.getContextAsync(Diagnostics, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
const info = await envinfo.run(
|
||||
{
|
||||
System: ['OS', 'Shell'],
|
||||
@@ -43,11 +38,12 @@ export default class Diagnostics extends EasCommand {
|
||||
);
|
||||
|
||||
Log.log(info.trimEnd());
|
||||
await this.printWorkflowAsync(projectDir);
|
||||
await this.printWorkflowAsync();
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
private async printWorkflowAsync(projectDir: string): Promise<void> {
|
||||
private async printWorkflowAsync(): Promise<void> {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const androidWorkflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
const iosWorkflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
@@ -9,6 +10,8 @@ import Log, { learnMore } from '../../log';
|
||||
import { createMetadataContextAsync } from '../../metadata/context';
|
||||
import { downloadMetadataAsync } from '../../metadata/download';
|
||||
import { handleMetadataError } from '../../metadata/errors';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
|
||||
export default class MetadataPull extends EasCommand {
|
||||
static override description = 'generate the local store configuration from the app stores';
|
||||
@@ -21,30 +24,21 @@ export default class MetadataPull extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
||||
|
||||
const { flags } = await this.parse(MetadataPull);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectConfig: { exp, projectId, projectDir },
|
||||
} = await this.getContextAsync(MetadataPull, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
|
||||
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
||||
await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
projectInfo: { exp, projectId },
|
||||
exp,
|
||||
projectDir,
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||
@@ -7,6 +8,8 @@ import Log, { learnMore } from '../../log';
|
||||
import { createMetadataContextAsync } from '../../metadata/context';
|
||||
import { handleMetadataError } from '../../metadata/errors';
|
||||
import { uploadMetadataAsync } from '../../metadata/upload';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
|
||||
export default class MetadataPush extends EasCommand {
|
||||
static override description = 'sync the local store configuration to the app stores';
|
||||
@@ -19,30 +22,21 @@ export default class MetadataPush extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('EAS Metadata is in beta and subject to breaking changes.');
|
||||
|
||||
const { flags } = await this.parse(MetadataPush);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectConfig: { exp, projectId, projectDir },
|
||||
} = await this.getContextAsync(MetadataPush, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
|
||||
// this command is interactive (all nonInteractive flags passed to utility functions are false)
|
||||
await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
await ensureProjectConfiguredAsync({ projectDir, nonInteractive: false });
|
||||
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
projectInfo: { exp, projectId },
|
||||
exp,
|
||||
projectDir,
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
user: await ensureLoggedInAsync({ nonInteractive: false }),
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,26 +3,24 @@ import openBrowserAsync from 'better-opn';
|
||||
import { getProjectDashboardUrl } from '../build/utils/url';
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import { ora } from '../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../project/projectUtils';
|
||||
|
||||
export default class Open extends EasCommand {
|
||||
static override description = 'open the project page in a web browser';
|
||||
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
// this command is interactive by nature (only really run by humans in a terminal)
|
||||
// this ensures the project exists before opening the browser
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
const projectName = exp.slug;
|
||||
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
|
||||
import chalk from 'chalk';
|
||||
import { vol } from 'memfs';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
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';
|
||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { confirmAsync, promptAsync } from '../../../prompts';
|
||||
import ProjectInit from '../init';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../user/actions');
|
||||
jest.mock('../../../graphql/mutations/AppMutation');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: () => ({ succeed: () => {}, fail: () => {} }),
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
|
||||
|
||||
let originalProcessArgv: string[];
|
||||
|
||||
beforeAll(() => {
|
||||
originalProcessArgv = process.argv;
|
||||
process.argv = [];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.argv = originalProcessArgv;
|
||||
});
|
||||
|
||||
function mockTestProject(options: {
|
||||
configuredProjectId?: string;
|
||||
configuredOwner?: string;
|
||||
}): void {
|
||||
const projectRoot = '/test-project';
|
||||
const packageJSON: PackageJSONConfig = {
|
||||
name: 'testing123',
|
||||
version: '0.1.0',
|
||||
description: 'fake description',
|
||||
main: 'index.js',
|
||||
};
|
||||
|
||||
const appJSON: AppJSONConfig = {
|
||||
expo: {
|
||||
name: 'testing 123',
|
||||
version: '0.1.0',
|
||||
slug: 'testing-123',
|
||||
sdkVersion: '33.0.0',
|
||||
owner: options.configuredOwner,
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: options.configuredProjectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vol.fromJSON(
|
||||
{
|
||||
[projectRoot + '/package.json']: JSON.stringify(packageJSON, null, 2),
|
||||
[projectRoot + '/app.json']: JSON.stringify(appJSON, null, 2),
|
||||
},
|
||||
'/'
|
||||
);
|
||||
|
||||
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(LoggedInContextField.prototype, 'getValueAsync')
|
||||
.mockResolvedValue({ actor: jester, graphqlClient });
|
||||
}
|
||||
|
||||
const commandOptions = { root: '/test-project' } as any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe(ProjectInit.name, () => {
|
||||
describe('when id flag is provided', () => {
|
||||
describe('when it is already configured', () => {
|
||||
beforeEach(() => {
|
||||
mockTestProject({ configuredProjectId: '1234', configuredOwner: jester.accounts[0].name });
|
||||
});
|
||||
|
||||
describe('interactive', () => {
|
||||
it('is no-op if already configured for id', async () => {
|
||||
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('prompts to overwrite when different', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '12345');
|
||||
expect(confirmAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('aborts when prompt to overwrite is declined', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
await new ProjectInit(['--id', '12345'], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
expect(confirmAsync).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('force', () => {
|
||||
it('does not prompt to overwrite when different', async () => {
|
||||
await new ProjectInit(['--id', '12345', '--force'], {
|
||||
root: '/test-project',
|
||||
} as any).run();
|
||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '12345');
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-interactive', () => {
|
||||
it('aborts when different', async () => {
|
||||
await expect(
|
||||
new ProjectInit(['--id', '12345', '--non-interactive'], {
|
||||
root: '/test-project',
|
||||
} as any).run()
|
||||
).rejects.toThrowError(
|
||||
`Project is already linked to a different ID: ${chalk.bold(
|
||||
'1234'
|
||||
)}. Use --force flag to overwrite.`
|
||||
);
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it is not yet configured', () => {
|
||||
beforeEach(() => {
|
||||
mockTestProject({});
|
||||
});
|
||||
|
||||
it('configures', async () => {
|
||||
await new ProjectInit(['--id', '1234'], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '1234');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no arguments are provided', () => {
|
||||
describe('when it is already configured', () => {
|
||||
beforeEach(() => {
|
||||
mockTestProject({ configuredProjectId: '1234', configuredOwner: jester.accounts[0].name });
|
||||
});
|
||||
|
||||
it('does not configure', async () => {
|
||||
await new ProjectInit([], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it is not yet configured', () => {
|
||||
describe('when owner is specified', () => {
|
||||
beforeEach(() => {
|
||||
mockTestProject({ configuredOwner: jester.accounts[0].name });
|
||||
});
|
||||
|
||||
describe('when project exists on server', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(findProjectIdByAccountNameAndSlugNullableAsync).mockResolvedValue('123456');
|
||||
});
|
||||
|
||||
it('prompts for confirmation to link', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
await new ProjectInit([], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '123456');
|
||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not save when confirmation to link is denied', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrow(
|
||||
'Project ID configuration canceled. Re-run the command to select a different account/project.'
|
||||
);
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when project does not exist on server', () => {
|
||||
it('asks to create it', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
jest.mocked(AppMutation.createAppAsync).mockResolvedValue('0129');
|
||||
await new ProjectInit([], commandOptions).run();
|
||||
expect(saveProjectIdToAppConfigAsync).toHaveBeenCalledWith('/test-project', '0129');
|
||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not create it if declined', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
||||
'Project ID configuration canceled for @jester/testing-123.'
|
||||
);
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
expect(confirmAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when owner is not specified', () => {
|
||||
beforeEach(() => {
|
||||
mockTestProject({});
|
||||
});
|
||||
|
||||
it('prompts for an account', async () => {
|
||||
jest.mocked(promptAsync).mockResolvedValue({ account: { name: 'other' } });
|
||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
||||
`You don't have permission to create a new project on the other account and no matching project already exists on the account.`
|
||||
);
|
||||
expect(promptAsync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when the user does not have permission to create projects on account', async () => {
|
||||
mockTestProject({ configuredOwner: jester.accounts[1].name });
|
||||
jest.mocked(AppMutation.createAppAsync).mockResolvedValue('0129');
|
||||
await expect(new ProjectInit([], commandOptions).run()).rejects.toThrowError(
|
||||
`You don't have permission to create a new project on the other account and no matching project already exists on the account.`
|
||||
);
|
||||
expect(saveProjectIdToAppConfigAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,14 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
|
||||
async function projectInfoByIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appId: string
|
||||
): Promise<AppInfoQuery> {
|
||||
async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppInfoQuery, AppInfoQueryVariables>(
|
||||
@@ -36,20 +34,14 @@ async function projectInfoByIdAsync(
|
||||
export default class ProjectInfo extends EasCommand {
|
||||
static override description = 'information about the current project';
|
||||
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
const { app } = await projectInfoByIdAsync(graphqlClient, projectId);
|
||||
// this command is non-interactive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
|
||||
const { app } = await projectInfoByIdAsync(projectId);
|
||||
if (!app) {
|
||||
throw new Error(`Could not find project with id: ${projectId}`);
|
||||
}
|
||||
|
||||
@@ -1,235 +1,27 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
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';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { toAppPrivacy } from '../../project/projectUtils';
|
||||
import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { Actor } from '../../user/User';
|
||||
|
||||
type InitializeMethodOptions = {
|
||||
force: boolean;
|
||||
nonInteractive: boolean;
|
||||
};
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
|
||||
export default class ProjectInit extends EasCommand {
|
||||
static override description = 'create or link an EAS project';
|
||||
static override aliases = ['init'];
|
||||
|
||||
static override flags = {
|
||||
id: Flags.string({
|
||||
description: 'ID of the EAS project to link',
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Whether to overwrite any existing project ID',
|
||||
dependsOn: ['id'],
|
||||
}),
|
||||
// this is the same as EASNonInteractiveFlag but with the dependsOn
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
dependsOn: ['id'],
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
private static async saveProjectIdAndLogSuccessAsync(
|
||||
projectDir: string,
|
||||
projectId: string
|
||||
): Promise<void> {
|
||||
await saveProjectIdToAppConfigAsync(projectDir, projectId);
|
||||
Log.withTick(`Project successfully linked (ID: ${chalk.bold(projectId)}) (modified app.json)`);
|
||||
}
|
||||
|
||||
private static async initializeWithExplicitIDAsync(
|
||||
projectId: string,
|
||||
projectDir: string,
|
||||
{ force, nonInteractive }: InitializeMethodOptions
|
||||
): Promise<void> {
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const existingProjectId = exp.extra?.eas?.projectId;
|
||||
|
||||
if (projectId === existingProjectId) {
|
||||
Log.succeed(`Project already linked (ID: ${chalk.bold(existingProjectId)})`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existingProjectId) {
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, projectId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (projectId !== existingProjectId) {
|
||||
if (force) {
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, projectId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
`Project is already linked to a different ID: ${chalk.bold(
|
||||
existingProjectId
|
||||
)}. Use --force flag to overwrite.`
|
||||
);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: `Project is already linked to a different ID: ${chalk.bold(
|
||||
existingProjectId
|
||||
)}. Do you wish to overwrite it?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
Log.log('Aborting');
|
||||
return;
|
||||
}
|
||||
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, projectId);
|
||||
}
|
||||
}
|
||||
|
||||
private static async initializeWithInteractiveSelectionAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
actor: Actor,
|
||||
projectDir: string
|
||||
): Promise<void> {
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const existingProjectId = exp.extra?.eas?.projectId;
|
||||
|
||||
if (existingProjectId) {
|
||||
Log.succeed(
|
||||
`Project already linked (ID: ${chalk.bold(
|
||||
existingProjectId
|
||||
)}). To re-configure, remove the "extra.eas.projectId" field from your app config.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const allAccounts = actor.accounts;
|
||||
const accountNamesWhereUserHasSufficientPermissionsToCreateApp = new Set(
|
||||
allAccounts
|
||||
.filter(a => a.users.find(it => it.actor.id === actor.id)?.role !== Role.ViewOnly)
|
||||
.map(it => it.name)
|
||||
);
|
||||
|
||||
// if no owner field, ask the user which account they want to use to create/link the project
|
||||
let accountName = exp.owner;
|
||||
if (!accountName) {
|
||||
if (allAccounts.length === 1) {
|
||||
accountName = allAccounts[0].name;
|
||||
} else {
|
||||
// if regular user, put primary account first
|
||||
const sortedAccounts =
|
||||
actor.__typename === 'Robot'
|
||||
? allAccounts
|
||||
: [...allAccounts].sort((a, _b) => (a.name === actor.username ? -1 : 1));
|
||||
|
||||
const choices = sortedAccounts.map(account => ({
|
||||
title: account.name,
|
||||
value: account,
|
||||
description: !accountNamesWhereUserHasSufficientPermissionsToCreateApp.has(account.name)
|
||||
? '(Viewer Role)'
|
||||
: undefined,
|
||||
}));
|
||||
accountName = (
|
||||
await promptAsync({
|
||||
type: 'select',
|
||||
name: 'account',
|
||||
message: 'Which account should own this project?',
|
||||
choices,
|
||||
})
|
||||
).account.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accountName) {
|
||||
throw new Error('No account selected for project. Canceling.');
|
||||
}
|
||||
|
||||
const projectName = exp.slug;
|
||||
const projectFullName = `@${accountName}/${projectName}`;
|
||||
const existingProjectIdOnServer = await findProjectIdByAccountNameAndSlugNullableAsync(
|
||||
graphqlClient,
|
||||
accountName,
|
||||
projectName
|
||||
);
|
||||
if (existingProjectIdOnServer) {
|
||||
const affirmedLink = await confirmAsync({
|
||||
message: `Existing project found: ${projectFullName} (ID: ${existingProjectIdOnServer}). Link this project?`,
|
||||
});
|
||||
if (!affirmedLink) {
|
||||
throw new Error(
|
||||
`Project ID configuration canceled. Re-run the command to select a different account/project.`
|
||||
);
|
||||
}
|
||||
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, existingProjectIdOnServer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accountNamesWhereUserHasSufficientPermissionsToCreateApp.has(accountName)) {
|
||||
throw new Error(
|
||||
`You don't have permission to create a new project on the ${accountName} account and no matching project already exists on the account.`
|
||||
);
|
||||
}
|
||||
|
||||
const affirmedCreate = await confirmAsync({
|
||||
message: `Would you like to create a project for ${projectFullName}?`,
|
||||
});
|
||||
if (!affirmedCreate) {
|
||||
throw new Error(`Project ID configuration canceled for ${projectFullName}.`);
|
||||
}
|
||||
|
||||
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
|
||||
const projectLink = terminalLink(projectFullName, projectDashboardUrl, {
|
||||
// https://github.com/sindresorhus/terminal-link/issues/18#issuecomment-1068020361
|
||||
fallback: () => `${projectFullName} (${projectDashboardUrl})`,
|
||||
});
|
||||
|
||||
const account = nullthrows(allAccounts.find(a => a.name === accountName));
|
||||
|
||||
const spinner = ora(`Creating ${chalk.bold(projectFullName)}`).start();
|
||||
let createdProjectId: string;
|
||||
try {
|
||||
createdProjectId = await AppMutation.createAppAsync(graphqlClient, {
|
||||
accountId: account.id,
|
||||
projectName,
|
||||
privacy: toAppPrivacy(exp.privacy) ?? AppPrivacy.Public,
|
||||
});
|
||||
spinner.succeed(`Created ${chalk.bold(projectLink)}`);
|
||||
} catch (err) {
|
||||
spinner.fail();
|
||||
throw err;
|
||||
}
|
||||
|
||||
await ProjectInit.saveProjectIdAndLogSuccessAsync(projectDir, createdProjectId);
|
||||
}
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags: { id, force, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ProjectInit);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectDir,
|
||||
} = await this.getContextAsync(ProjectInit, { nonInteractive });
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
if (id) {
|
||||
await ProjectInit.initializeWithExplicitIDAsync(id, projectDir, { force, nonInteractive });
|
||||
} else {
|
||||
await ProjectInit.initializeWithInteractiveSelectionAsync(graphqlClient, actor, projectDir);
|
||||
if (exp.extra?.eas?.projectId) {
|
||||
Log.error(
|
||||
`app.json is already linked to project with id: ${chalk.bold(exp.extra?.eas?.projectId)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// this command is interactive by nature
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: false });
|
||||
Log.withTick(`Linked app.json to project with id ${chalk.bold(projectId)}`);
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,12 @@ import {
|
||||
SecretTypeToEnvironmentSecretType,
|
||||
} from '../../graphql/types/EnvironmentSecret';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
|
||||
@@ -49,11 +52,6 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: {
|
||||
@@ -65,15 +63,12 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
'non-interactive': nonInteractive,
|
||||
},
|
||||
} = await this.parse(EnvironmentSecretCreate);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentSecretCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const ownerAccount = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
const ownerAccount = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
if (!scope) {
|
||||
const validationMessage = 'Secret scope may not be empty.';
|
||||
@@ -174,19 +169,17 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
if (!(await fs.pathExists(secretFilePath))) {
|
||||
throw new Error(`File "${secretValue}" does not exist`);
|
||||
}
|
||||
secretValue = await fs.readFile(secretFilePath, 'base64');
|
||||
}
|
||||
|
||||
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(graphqlClient, existingSecret.id);
|
||||
await EnvironmentSecretMutation.deleteAsync(existingSecret.id);
|
||||
Log.withTick(
|
||||
`Deleting existing secret ${chalk.bold(name)} on project ${chalk.bold(
|
||||
projectDisplayName
|
||||
@@ -196,7 +189,6 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
}
|
||||
|
||||
const secret = await EnvironmentSecretMutation.createForAppAsync(
|
||||
graphqlClient,
|
||||
{ name, value: secretValue, type: SecretTypeToEnvironmentSecretType[secretType] },
|
||||
projectId
|
||||
);
|
||||
@@ -222,13 +214,12 @@ 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(graphqlClient, existingSecret.id);
|
||||
await EnvironmentSecretMutation.deleteAsync(existingSecret.id);
|
||||
|
||||
Log.withTick(
|
||||
`Deleting existing secret ${chalk.bold(name)} on account ${chalk.bold(
|
||||
@@ -239,7 +230,6 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
}
|
||||
|
||||
const secret = await EnvironmentSecretMutation.createForAccountAsync(
|
||||
graphqlClient,
|
||||
{ name, value: secretValue, type: SecretTypeToEnvironmentSecretType[secretType] },
|
||||
ownerAccount.id
|
||||
);
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
|
||||
export default class EnvironmentSecretDelete extends EasCommand {
|
||||
@@ -21,30 +23,23 @@ export default class EnvironmentSecretDelete extends EasCommand {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { id, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentSecretDelete);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentSecretDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
let secret: EnvironmentSecretWithScope | undefined;
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
if (!id) {
|
||||
const validationMessage = 'You must select which secret to delete.';
|
||||
if (nonInteractive) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(graphqlClient, projectId);
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
|
||||
({ secret } = await promptAsync({
|
||||
type: 'autocomplete',
|
||||
name: 'secret',
|
||||
@@ -85,7 +80,7 @@ export default class EnvironmentSecretDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
await EnvironmentSecretMutation.deleteAsync(graphqlClient, id);
|
||||
await EnvironmentSecretMutation.deleteAsync(id);
|
||||
|
||||
Log.withTick(`️Deleted secret${secret?.name ? ` "${secret?.name}"` : ''} with id "${id}".`);
|
||||
}
|
||||
|
||||
@@ -6,24 +6,23 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EnvironmentSecretsQuery } from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import { EnvironmentSecretTypeToSecretType } from '../../graphql/types/EnvironmentSecret';
|
||||
import Log from '../../log';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
|
||||
export default class EnvironmentSecretList extends EasCommand {
|
||||
static override description = 'list environment secrets available for your current app';
|
||||
|
||||
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 projectDir = await findProjectRootAsync();
|
||||
if (!projectDir) {
|
||||
throw new Error("Run this command inside your project's directory");
|
||||
}
|
||||
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(graphqlClient, projectId);
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
// this command is non-interacive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectId);
|
||||
|
||||
const table = new Table({
|
||||
head: ['Name', 'Type', 'Scope', 'ID', 'Updated at'],
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
selectRequestedPlatformAsync,
|
||||
toPlatforms,
|
||||
} from '../platform';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../project/projectUtils';
|
||||
import { SubmitArchiveFlags, createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
|
||||
@@ -89,24 +91,16 @@ export default class Submit extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(Submit);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
projectConfig: { exp, projectId, projectDir },
|
||||
} = await this.getContextAsync(Submit, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
const flags = this.sanitizeFlags(rawFlags);
|
||||
|
||||
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasSubmit]);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: flags.nonInteractive });
|
||||
|
||||
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasSubmit]);
|
||||
|
||||
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
||||
|
||||
@@ -120,17 +114,13 @@ export default class Submit extends EasCommand {
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
for (const submissionProfile of submissionProfiles) {
|
||||
// this command doesn't make use of env when getting the project config
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: submissionProfile.platform,
|
||||
projectDir,
|
||||
projectId,
|
||||
profile: submissionProfile.profile,
|
||||
archiveFlags: flagsWithPlatform.archiveFlags,
|
||||
nonInteractive: flagsWithPlatform.nonInteractive,
|
||||
actor,
|
||||
graphqlClient,
|
||||
exp,
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (submissionProfiles.length > 1) {
|
||||
@@ -151,7 +141,7 @@ export default class Submit extends EasCommand {
|
||||
printSubmissionDetailsUrls(submissions);
|
||||
|
||||
if (flagsWithPlatform.wait) {
|
||||
const completedSubmissions = await waitToCompleteAsync(graphqlClient, submissions, {
|
||||
const completedSubmissions = await waitToCompleteAsync(submissions, {
|
||||
verbose: flagsWithPlatform.verbose,
|
||||
});
|
||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
|
||||
|
||||
@@ -9,7 +9,10 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { AppPlatform } from '../../graphql/generated';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { RequestedPlatform, appPlatformDisplayNames } from '../../platform';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectIdAsync,
|
||||
installExpoUpdatesAsync,
|
||||
isExpoUpdatesInstalledOrAvailable,
|
||||
} from '../../project/projectUtils';
|
||||
@@ -32,23 +35,14 @@ export default class UpdateConfigure extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.log(
|
||||
'💡 The following process will configure your project to run EAS Update. These changes only apply to your local project files and you can safely revert them at any time.'
|
||||
);
|
||||
const { flags } = await this.parse(UpdateConfigure);
|
||||
const {
|
||||
projectConfig: { projectId, exp, projectDir },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateConfigure, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const platform = flags.platform as RequestedPlatform;
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
if (!isExpoUpdatesInstalledOrAvailable(projectDir, exp.sdkVersion)) {
|
||||
await installExpoUpdatesAsync(projectDir);
|
||||
@@ -67,7 +61,6 @@ export default class UpdateConfigure extends EasCommand {
|
||||
android: androidWorkflow,
|
||||
ios: iosWorkflow,
|
||||
},
|
||||
projectId,
|
||||
});
|
||||
Log.withTick(`Configured ${chalk.bold('app.json')} for EAS Update`);
|
||||
|
||||
@@ -76,14 +69,14 @@ export default class UpdateConfigure extends EasCommand {
|
||||
[RequestedPlatform.Android, RequestedPlatform.All].includes(platform) &&
|
||||
androidWorkflow === Workflow.GENERIC
|
||||
) {
|
||||
await syncAndroidUpdatesConfigurationAsync(graphqlClient, projectDir, updatedExp, projectId);
|
||||
await syncAndroidUpdatesConfigurationAsync(projectDir, updatedExp, { nonInteractive: true });
|
||||
Log.withTick(`Configured ${chalk.bold('AndroidManifest.xml')} for EAS Update`);
|
||||
}
|
||||
if (
|
||||
[RequestedPlatform.Ios, RequestedPlatform.All].includes(platform) &&
|
||||
iosWorkflow === Workflow.GENERIC
|
||||
) {
|
||||
await syncIosUpdatesConfigurationAsync(graphqlClient, projectDir, updatedExp, projectId);
|
||||
await syncIosUpdatesConfigurationAsync(projectDir, updatedExp, { nonInteractive: true });
|
||||
Log.withTick(`Configured ${chalk.bold('Expo.plist')} for EAS Update`);
|
||||
}
|
||||
|
||||
@@ -97,7 +90,6 @@ async function configureAppJSONForEASUpdateAsync({
|
||||
exp,
|
||||
platform,
|
||||
workflows,
|
||||
projectId,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
@@ -105,9 +97,9 @@ async function configureAppJSONForEASUpdateAsync({
|
||||
workflows: {
|
||||
[key in RequestedPlatform.Android | RequestedPlatform.Ios]: Workflow;
|
||||
};
|
||||
projectId: string;
|
||||
}): Promise<ExpoConfig> {
|
||||
// this command is non-interactive in the way it was designed
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
const easUpdateURL = getEASUpdateURL(projectId);
|
||||
const updates = { ...exp.updates, url: easUpdateURL };
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ 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 { withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
DeleteUpdateGroupMutation,
|
||||
UpdateMutationDeleteUpdateGroupArgs,
|
||||
@@ -13,14 +12,11 @@ import Log from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
async function deleteUpdateGroupAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
group,
|
||||
}: {
|
||||
group: string;
|
||||
}
|
||||
): Promise<DeleteUpdateGroupMutation> {
|
||||
async function deleteUpdateGroupAsync({
|
||||
group,
|
||||
}: {
|
||||
group: string;
|
||||
}): Promise<DeleteUpdateGroupMutation> {
|
||||
return await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteUpdateGroupMutation, UpdateMutationDeleteUpdateGroupArgs>(
|
||||
@@ -54,20 +50,12 @@ 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();
|
||||
}
|
||||
@@ -90,7 +78,7 @@ export default class UpdateDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
await deleteUpdateGroupAsync(graphqlClient, { group });
|
||||
await deleteUpdateGroupAsync({ group });
|
||||
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput({ group });
|
||||
|
||||
@@ -11,7 +11,6 @@ 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';
|
||||
@@ -28,8 +27,11 @@ import { BranchQuery } from '../../graphql/queries/BranchQuery';
|
||||
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
installExpoUpdatesAsync,
|
||||
isExpoUpdatesInstalledOrAvailable,
|
||||
} from '../../project/projectUtils';
|
||||
@@ -61,20 +63,17 @@ import { createUpdateChannelOnAppAsync } from '../channel/create';
|
||||
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
|
||||
export type PublishPlatformFlag = PublishPlatform | 'all';
|
||||
|
||||
async function ensureChannelExistsAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
branchId,
|
||||
channelName,
|
||||
}: {
|
||||
appId: string;
|
||||
branchId: string;
|
||||
channelName: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
async function ensureChannelExistsAsync({
|
||||
appId,
|
||||
branchId,
|
||||
channelName,
|
||||
}: {
|
||||
appId: string;
|
||||
branchId: string;
|
||||
channelName: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await createUpdateChannelOnAppAsync(graphqlClient, {
|
||||
await createUpdateChannelOnAppAsync({
|
||||
appId,
|
||||
channelName,
|
||||
branchId,
|
||||
@@ -92,29 +91,26 @@ async function ensureChannelExistsAsync(
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureBranchExistsAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name: branchName }: ViewBranchQueryVariables
|
||||
): Promise<{
|
||||
export async function ensureBranchExistsAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
}: ViewBranchQueryVariables): Promise<{
|
||||
branchId: string;
|
||||
}> {
|
||||
try {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync({
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
const { id } = updateBranch;
|
||||
await ensureChannelExistsAsync(graphqlClient, { appId, branchId: id, channelName: branchName });
|
||||
await ensureChannelExistsAsync({ appId, branchId: id, channelName: branchName });
|
||||
return { branchId: id };
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync({ appId, name: branchName });
|
||||
Log.withTick(`Created branch: ${chalk.bold(branchName)}`);
|
||||
await ensureChannelExistsAsync(graphqlClient, {
|
||||
await ensureChannelExistsAsync({
|
||||
appId,
|
||||
branchId: newUpdateBranch.id,
|
||||
channelName: branchName,
|
||||
@@ -173,11 +169,6 @@ export default class UpdatePublish extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(UpdatePublish);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
@@ -194,12 +185,6 @@ export default class UpdatePublish extends EasCommand {
|
||||
'non-interactive': nonInteractive,
|
||||
json: jsonFlag,
|
||||
} = flags;
|
||||
const {
|
||||
getDynamicProjectConfigAsync,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdatePublish, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
@@ -209,15 +194,16 @@ export default class UpdatePublish extends EasCommand {
|
||||
// If a group was specified, that means we are republishing it.
|
||||
republish = republish || !!group;
|
||||
|
||||
const { exp, projectId, projectDir } = await getDynamicProjectConfigAsync({
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir, {
|
||||
isPublicConfig: true,
|
||||
});
|
||||
|
||||
const { exp: expPrivate } = await getDynamicProjectConfigAsync({
|
||||
const expPrivate = getExpoConfig(projectDir, {
|
||||
isPublicConfig: false,
|
||||
});
|
||||
|
||||
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
|
||||
await maybeWarnAboutEasOutagesAsync([StatuspageServiceName.EasUpdate]);
|
||||
|
||||
const codeSigningInfo = await getCodeSigningInfoAsync(expPrivate, privateKeyPath);
|
||||
|
||||
@@ -248,6 +234,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync(exp, platformFlag, projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
await checkEASUpdateURLIsSetAsync(exp, projectId);
|
||||
|
||||
if (!branchName) {
|
||||
@@ -257,7 +244,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(graphqlClient, {
|
||||
const branch = await selectBranchOnAppAsync({
|
||||
projectId,
|
||||
promptTitle: `Which branch would you like to ${
|
||||
republish ? 'republish' : 'publish'
|
||||
@@ -297,16 +284,14 @@ export default class UpdatePublish extends EasCommand {
|
||||
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
|
||||
>[];
|
||||
if (group) {
|
||||
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
|
||||
groupId: group,
|
||||
});
|
||||
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync({ groupId: group });
|
||||
updatesToRepublish = updatesByGroup;
|
||||
} else {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --group when in non-interactive mode');
|
||||
}
|
||||
|
||||
updatesToRepublish = await selectUpdateGroupOnBranchAsync(graphqlClient, {
|
||||
updatesToRepublish = await selectUpdateGroupOnBranchAsync({
|
||||
projectId,
|
||||
branchName,
|
||||
paginatedQueryOptions,
|
||||
@@ -393,7 +378,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
if (!skipBundler) {
|
||||
const bundleSpinner = ora().start('Building bundle...');
|
||||
try {
|
||||
await buildBundlesAsync({ projectDir, inputDir });
|
||||
await buildBundlesAsync({ projectDir, inputDir, platform: platformFlag });
|
||||
bundleSpinner.succeed('Built bundle!');
|
||||
} catch (e) {
|
||||
bundleSpinner.fail('Failed to build bundle!');
|
||||
@@ -406,7 +391,6 @@ 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) => {
|
||||
@@ -437,7 +421,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
.map(pair => pair[0]);
|
||||
}
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branchId } = await ensureBranchExistsAsync({
|
||||
appId: projectId,
|
||||
name: branchName,
|
||||
});
|
||||
@@ -469,7 +453,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups'];
|
||||
const publishSpinner = ora('Publishing...').start();
|
||||
try {
|
||||
newUpdates = await PublishMutation.publishUpdateGroupAsync(graphqlClient, updateGroups);
|
||||
newUpdates = await PublishMutation.publishUpdateGroupAsync(updateGroups);
|
||||
|
||||
if (codeSigningInfo) {
|
||||
Log.log('🔒 Signing updates');
|
||||
@@ -502,7 +486,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
|
||||
const manifestSignature = signManifestBody(manifestBody, codeSigningInfo);
|
||||
|
||||
await PublishMutation.setCodeSigningInfoAsync(graphqlClient, newUpdate.id, {
|
||||
await PublishMutation.setCodeSigningInfoAsync(newUpdate.id, {
|
||||
alg: codeSigningInfo.codeSigningMetadata.alg,
|
||||
keyid: codeSigningInfo.codeSigningMetadata.keyid,
|
||||
sig: manifestSignature,
|
||||
@@ -515,7 +499,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
|
||||
publishSpinner.succeed('Published!');
|
||||
} catch (e) {
|
||||
publishSpinner.fail('Failed to publish updates');
|
||||
publishSpinner.fail('Failed to published updates');
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -545,7 +529,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
const updateGroupId = newUpdatesForRuntimeVersion[0].group;
|
||||
|
||||
const projectName = exp.slug;
|
||||
const accountName = (await getOwnerAccountForProjectIdAsync(graphqlClient, projectId)).name;
|
||||
const accountName = (await getOwnerAccountForProjectIdAsync(projectId)).name;
|
||||
const updateGroupUrl = getUpdateGroupUrl(accountName, projectName, updateGroupId);
|
||||
const updateGroupLink = link(updateGroupUrl, { dim: false });
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
getLimitFlagWithCustomValues,
|
||||
getPaginatedQueryOptions,
|
||||
} from '../../commandUtils/pagination';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import {
|
||||
listAndRenderUpdateGroupsOnAppAsync,
|
||||
listAndRenderUpdateGroupsOnBranchAsync,
|
||||
@@ -32,31 +34,24 @@ export default class UpdateList extends EasCommand {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(UpdateList);
|
||||
const { branch: branchFlag, all, json: jsonFlag, 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateList, {
|
||||
nonInteractive,
|
||||
});
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
if (all) {
|
||||
listAndRenderUpdateGroupsOnAppAsync(graphqlClient, { projectId, paginatedQueryOptions });
|
||||
listAndRenderUpdateGroupsOnAppAsync({ projectId, paginatedQueryOptions });
|
||||
} else {
|
||||
if (branchFlag) {
|
||||
listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
|
||||
listAndRenderUpdateGroupsOnBranchAsync({
|
||||
projectId,
|
||||
branchName: branchFlag,
|
||||
paginatedQueryOptions,
|
||||
@@ -67,7 +62,7 @@ export default class UpdateList extends EasCommand {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const selectedBranch = await selectBranchOnAppAsync(graphqlClient, {
|
||||
const selectedBranch = await selectBranchOnAppAsync({
|
||||
projectId,
|
||||
promptTitle: 'Which branch would you like to view?',
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
@@ -79,7 +74,7 @@ export default class UpdateList extends EasCommand {
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
listAndRenderUpdateGroupsOnBranchAsync(graphqlClient, {
|
||||
listAndRenderUpdateGroupsOnBranchAsync({
|
||||
projectId,
|
||||
branchName: selectedBranch.name,
|
||||
paginatedQueryOptions,
|
||||
|
||||
@@ -24,25 +24,16 @@ 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(graphqlClient, { groupId });
|
||||
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync({ groupId });
|
||||
const [updateGroupDescription] = getUpdateGroupDescriptions([updatesByGroup]);
|
||||
|
||||
if (jsonFlag) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { WebhookType } from '../../graphql/generated';
|
||||
import { WebhookMutation } from '../../graphql/mutations/WebhookMutation';
|
||||
import { ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { prepareInputParamsAsync } from '../../webhooks/input';
|
||||
|
||||
export default class WebhookCreate extends EasCommand {
|
||||
@@ -25,24 +27,18 @@ export default class WebhookCreate extends EasCommand {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
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'],
|
||||
});
|
||||
const webhookInputParams = await prepareInputParamsAsync(flags);
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: flags['non-interactive'] });
|
||||
|
||||
const spinner = ora('Creating a webhook').start();
|
||||
try {
|
||||
await WebhookMutation.createWebhookAsync(graphqlClient, projectId, webhookInputParams);
|
||||
await WebhookMutation.createWebhookAsync(projectId, webhookInputParams);
|
||||
spinner.succeed('Successfully created a webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to create a webhook');
|
||||
|
||||
@@ -3,13 +3,14 @@ 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';
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
@@ -28,27 +29,20 @@ export default class WebhookDelete extends EasCommand {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
args: { ID: webhookId },
|
||||
flags: { 'non-interactive': nonInteractive },
|
||||
} = await this.parse(WebhookDelete);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(WebhookDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive });
|
||||
|
||||
let webhook: WebhookFragment | undefined =
|
||||
webhookId && (await WebhookQuery.byIdAsync(graphqlClient, webhookId));
|
||||
webhookId && (await WebhookQuery.byIdAsync(webhookId));
|
||||
if (!webhookId) {
|
||||
const webhooks = await fetchWebhooksByAppIdAsync(graphqlClient, projectId);
|
||||
const webhooks = await fetchWebhooksByAppIdAsync(projectId);
|
||||
if (webhooks.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -89,7 +83,7 @@ export default class WebhookDelete extends EasCommand {
|
||||
|
||||
const spinner = ora('Deleting the webhook').start();
|
||||
try {
|
||||
await WebhookMutation.deleteWebhookAsync(graphqlClient, webhookId);
|
||||
await WebhookMutation.deleteWebhookAsync(webhookId);
|
||||
spinner.succeed('Successfully deleted the webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to delete the webhook');
|
||||
@@ -98,13 +92,10 @@ export default class WebhookDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWebhooksByAppIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appId: string
|
||||
): Promise<WebhookFragment[]> {
|
||||
async function fetchWebhooksByAppIdAsync(appId: string): Promise<WebhookFragment[]> {
|
||||
const spinner = ora('Fetching webhooks').start();
|
||||
try {
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(graphqlClient, appId);
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(appId);
|
||||
if (webhooks.length === 0) {
|
||||
spinner.fail('There are no webhooks on the project');
|
||||
return [];
|
||||
|
||||
@@ -6,7 +6,12 @@ import { WebhookType } from '../../graphql/generated';
|
||||
import { WebhookQuery } from '../../graphql/queries/WebhookQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getDisplayNameForProjectIdAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
export default class WebhookList extends EasCommand {
|
||||
@@ -19,31 +24,21 @@ export default class WebhookList extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags: { event },
|
||||
} = await this.parse(WebhookList);
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(WebhookList, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const exp = getExpoConfig(projectDir);
|
||||
|
||||
// this command is non-interactive by design
|
||||
const projectId = await getProjectIdAsync(exp, { nonInteractive: true });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(projectId);
|
||||
|
||||
const spinner = ora(`Fetching the list of webhook on project ${projectDisplayName}`).start();
|
||||
try {
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(
|
||||
graphqlClient,
|
||||
projectId,
|
||||
event && { event }
|
||||
);
|
||||
const webhooks = await WebhookQuery.byAppIdAsync(projectId, event && { event });
|
||||
if (webhooks.length === 0) {
|
||||
spinner.fail(`There are no webhooks on project ${projectDisplayName}`);
|
||||
} else {
|
||||
|
||||
@@ -31,19 +31,12 @@ 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(graphqlClient, webhookId);
|
||||
const webhook = await WebhookQuery.byIdAsync(webhookId);
|
||||
const webhookInputParams = await prepareInputParamsAsync(
|
||||
pick(flags, ['event', 'url', 'secret', 'non-interactive']),
|
||||
webhook
|
||||
@@ -51,7 +44,7 @@ export default class WebhookUpdate extends EasCommand {
|
||||
|
||||
const spinner = ora('Updating a webhook').start();
|
||||
try {
|
||||
await WebhookMutation.updateWebhookAsync(graphqlClient, webhookId, webhookInputParams);
|
||||
await WebhookMutation.updateWebhookAsync(webhookId, webhookInputParams);
|
||||
spinner.succeed('Successfully updated a webhook');
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to update a webhook');
|
||||
|
||||
@@ -15,21 +15,14 @@ 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(graphqlClient, webhookId);
|
||||
const webhook = await WebhookQuery.byIdAsync(webhookId);
|
||||
spinner.succeed(`Found the webhook details`);
|
||||
Log.log(`\n${formatWebhook(webhook)}`);
|
||||
} catch (err) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
AppFragment,
|
||||
CommonAndroidAppCredentialsFragment,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
Role,
|
||||
} from '../../graphql/generated';
|
||||
import * as AndroidGraphqlClient from '../android/api/GraphqlClient';
|
||||
import { Keystore } from '../android/credentials';
|
||||
@@ -59,14 +58,6 @@ export const testAppFragment: AppFragment = {
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
name: 'testuser',
|
||||
users: [
|
||||
{
|
||||
role: Role.Owner,
|
||||
actor: {
|
||||
id: 'test-user-id',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,40 +1,19 @@
|
||||
import { AppFragment, Role } from '../../graphql/generated';
|
||||
import { AppFragment } from '../../graphql/generated';
|
||||
import { Actor } from '../../user/User';
|
||||
|
||||
export const jester = {
|
||||
__typename: 'User' as const,
|
||||
export const jester: Actor = {
|
||||
__typename: 'User',
|
||||
id: 'jester-id',
|
||||
username: 'jester',
|
||||
primaryAccount: {
|
||||
id: 'jester-account-id',
|
||||
name: 'jester',
|
||||
users: [{ role: Role.Admin, actor: { id: 'jester-id' } }],
|
||||
},
|
||||
accounts: [
|
||||
{
|
||||
id: 'jester-account-id',
|
||||
name: 'jester',
|
||||
users: [{ role: Role.Admin, actor: { id: 'jester-id' } }],
|
||||
},
|
||||
{
|
||||
id: 'other-account-id',
|
||||
name: 'other',
|
||||
users: [{ role: Role.ViewOnly, actor: { id: 'jester-id' } }],
|
||||
},
|
||||
],
|
||||
accounts: [{ id: 'jester-account-id', name: 'jester' }],
|
||||
isExpoAdmin: false,
|
||||
};
|
||||
|
||||
export const jester2 = {
|
||||
export const jester2: Actor = {
|
||||
__typename: 'User',
|
||||
id: 'jester2-id',
|
||||
username: 'jester2',
|
||||
accounts: [
|
||||
{
|
||||
id: 'jester2-account-id',
|
||||
name: 'jester2',
|
||||
users: [{ role: Role.Admin, actor: { id: 'jester2-id' } }],
|
||||
},
|
||||
],
|
||||
accounts: [{ id: 'jester2-account-id', name: 'jester2' }],
|
||||
isExpoAdmin: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
CommonIosAppCredentialsFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
Role,
|
||||
} from '../../graphql/generated';
|
||||
import * as IosGraphqlClient from '../ios/api/GraphqlClient';
|
||||
import {
|
||||
@@ -59,14 +58,6 @@ export const testAppFragment: AppFragment = {
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
name: 'testuser',
|
||||
users: [
|
||||
{
|
||||
role: Role.Owner,
|
||||
actor: {
|
||||
id: 'test-user-id',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -14,12 +14,8 @@ export class AssignFcm {
|
||||
fcm: AndroidFcmFragment
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
const appCredentials =
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
this.app
|
||||
);
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(this.app);
|
||||
const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
appCredentials,
|
||||
{
|
||||
androidFcmId: fcm.id,
|
||||
|
||||
@@ -14,12 +14,8 @@ export class AssignGoogleServiceAccountKey {
|
||||
googleServiceAccountKey: GoogleServiceAccountKeyFragment
|
||||
): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
const appCredentials =
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
this.app
|
||||
);
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(this.app);
|
||||
const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
appCredentials,
|
||||
{
|
||||
googleServiceAccountKeyForSubmissionsId: googleServiceAccountKey.id,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { GradleBuildContext } from '../../../project/android/gradle';
|
||||
import {
|
||||
getOwnerAccountForProjectIdAsync,
|
||||
getProjectConfigDescription,
|
||||
getProjectIdAsync,
|
||||
} from '../../../project/projectUtils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
@@ -21,16 +22,13 @@ export async function canCopyLegacyCredentialsAsync(
|
||||
ctx: CredentialsContext,
|
||||
app: AppLookupParams
|
||||
): Promise<boolean> {
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
||||
ctx.graphqlClient,
|
||||
app
|
||||
);
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(app);
|
||||
if (appCredentials) {
|
||||
return false; // modern credentials already exist
|
||||
}
|
||||
|
||||
const legacyAppCredentials =
|
||||
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(ctx.graphqlClient, app);
|
||||
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(app);
|
||||
return !!legacyAppCredentials; // user has some legacy credentials
|
||||
}
|
||||
|
||||
@@ -47,37 +45,30 @@ export async function promptUserAndCopyLegacyCredentialsAsync(
|
||||
|
||||
try {
|
||||
const legacyAppCredentials =
|
||||
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(ctx.graphqlClient, app);
|
||||
await ctx.android.getLegacyAndroidAppCredentialsWithCommonFieldsAsync(app);
|
||||
if (!legacyAppCredentials) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appCredentials =
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
app
|
||||
);
|
||||
await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(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(ctx.graphqlClient, appCredentials, {
|
||||
await ctx.android.updateAndroidAppCredentialsAsync(appCredentials, {
|
||||
androidFcmId: clonedFcm.id,
|
||||
});
|
||||
}
|
||||
|
||||
const legacyBuildCredentials = await ctx.android.getLegacyAndroidAppBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
app
|
||||
);
|
||||
const legacyBuildCredentials = await ctx.android.getLegacyAndroidAppBuildCredentialsAsync(app);
|
||||
const legacyKeystore = legacyBuildCredentials?.androidKeystore ?? null;
|
||||
|
||||
if (legacyKeystore) {
|
||||
const clonedKeystore = await ctx.android.createKeystoreAsync(ctx.graphqlClient, app.account, {
|
||||
const clonedKeystore = await ctx.android.createKeystoreAsync(app.account, {
|
||||
keystore: legacyKeystore.keystore,
|
||||
keystorePassword: legacyKeystore.keystorePassword,
|
||||
keyAlias: legacyKeystore.keyAlias,
|
||||
@@ -102,8 +93,8 @@ export async function getAppLookupParamsFromContextAsync(
|
||||
): Promise<AppLookupParams> {
|
||||
ctx.ensureProjectContext();
|
||||
const projectName = ctx.exp.slug;
|
||||
const projectId = ctx.projectId;
|
||||
const account = await getOwnerAccountForProjectIdAsync(ctx.graphqlClient, projectId);
|
||||
const projectId = await getProjectIdAsync(ctx.exp, { nonInteractive: ctx.nonInteractive });
|
||||
const account = await getOwnerAccountForProjectIdAsync(projectId);
|
||||
|
||||
const androidApplicationIdentifier = await getApplicationIdAsync(
|
||||
ctx.projectDir,
|
||||
@@ -135,23 +126,18 @@ export async function createOrUpdateDefaultAndroidAppBuildCredentialsAsync(
|
||||
'createOrUpdateDefaultAndroidAppBuildCredentialsAsync must be run in interactive mode'
|
||||
);
|
||||
const existingDefaultBuildCredentials =
|
||||
await ctx.android.getDefaultAndroidAppBuildCredentialsAsync(ctx.graphqlClient, appLookupParams);
|
||||
await ctx.android.getDefaultAndroidAppBuildCredentialsAsync(appLookupParams);
|
||||
if (existingDefaultBuildCredentials) {
|
||||
return await ctx.android.updateAndroidAppBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
existingDefaultBuildCredentials,
|
||||
{ androidKeystoreId }
|
||||
);
|
||||
}
|
||||
return await ctx.android.createAndroidAppBuildCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
appLookupParams,
|
||||
{
|
||||
name: generateRandomName(),
|
||||
isDefault: true,
|
||||
androidKeystoreId,
|
||||
}
|
||||
);
|
||||
return await ctx.android.createAndroidAppBuildCredentialsAsync(appLookupParams, {
|
||||
name: generateRandomName(),
|
||||
isDefault: true,
|
||||
androidKeystoreId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function promptForNameAsync(): Promise<string> {
|
||||
|
||||
Loaded 100 of 284 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user