mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1de239b6ba | ||
|
|
4515d29907 | ||
|
|
7591cac883 | ||
|
|
9765f75a7d | ||
|
|
1f1b58db82 | ||
|
|
b0c88a37a9 | ||
|
|
5694bb9fdc | ||
|
|
9245657e23 | ||
|
|
9ef8483882 | ||
|
|
b859e11126 | ||
|
|
cee499e0e6 | ||
|
|
8702f165f3 | ||
|
|
a6bea7e234 | ||
|
|
37343dd586 | ||
|
|
058183f202 | ||
|
|
7ee50de78e | ||
|
|
ca11aa9e41 | ||
|
|
64d985fcda | ||
|
|
bd2bb4dcf6 | ||
|
|
17db6c4cf1 | ||
|
|
5203096aca | ||
|
|
a53e1f916f | ||
|
|
09a4b3d387 | ||
|
|
24e7de5bfb | ||
|
|
613fb5b6ea | ||
|
|
4e117075fa | ||
|
|
33f4ae7a05 | ||
|
|
caba28901d | ||
|
|
77e02f5759 | ||
|
|
fa2717aada | ||
|
|
322f797cbe | ||
|
|
2a64b099ed | ||
|
|
81465733e8 | ||
|
|
9b22056205 | ||
|
|
24841f82e0 | ||
|
|
a8fae470d0 | ||
|
|
72283cdda2 | ||
|
|
9874ff26b3 | ||
|
|
85d64b7ad3 | ||
|
|
2dba2705b7 | ||
|
|
d1bec6e00a | ||
|
|
597e012ca3 |
No files matched your search
+70
-1
@@ -8,12 +8,81 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--channel` option to `update:republish`. ([#1580](https://github.com/expo/eas-cli/pull/1580) by [@byCedric](https://github.com/byCedric))
|
||||
- Use `appVersion` as default runtime version policy when running `eas update:configure`. ([#1588](https://github.com/expo/eas-cli/pull/1588) by [@jonsamp](https://github.com/jonsamp))
|
||||
- Support `--json` flag in webhook list command. ([#1605](https://github.com/expo/eas-cli/pull/1605) by [@sheddy7](https://github.com/sheddy7))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Makes eas.json configuration to only run on `update:configure`. ([#1598](https://github.com/expo/eas-cli/pull/1598) by [@jonsamp](https://github.com/jonsamp))
|
||||
- Fix issue with invisible build info in some terminals in the `eas build:run` and `eas build:resign` commands. ([#1602](https://github.com/expo/eas-cli/pull/1602) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Fix issues with invisible build info in some terminals while using the `eas submit` command. ([#1603](https://github.com/expo/eas-cli/pull/1603) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Use the `completedAt` timestamp as the build finish date instead of the `updatedAt` timestamp in the `eas build:run` command. ([#1604](https://github.com/expo/eas-cli/pull/1604) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Make all URLs in logs clickable in terminals supporting hyperlinks. ([#1591](https://github.com/expo/eas-cli/pull/1591) by [@Simek](https://github.com/Simek))
|
||||
- Use paginated select prompt for the `eas build:run` command. ([#1601](https://github.com/expo/eas-cli/pull/1601) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [3.1.1](https://github.com/expo/eas-cli/releases/tag/v3.1.1) - 2022-12-19
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix update `--channel` and `--branch` flag validation. ([#1579](https://github.com/expo/eas-cli/pull/1579) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [3.1.0](https://github.com/expo/eas-cli/releases/tag/v3.1.0) - 2022-12-17
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Adds `--channel` flag to `eas update`. ([#1567](https://github.com/expo/eas-cli/pull/1567) by [@jonsamp](https://github.com/jonsamp))
|
||||
- Add channel configurations to **eas.json** during `eas update:configure`. ([#1570](https://github.com/expo/eas-cli/pull/1570) by [@jonsamp](https://github.com/jonsamp))
|
||||
- Add `build:resign` command. ([#1575](https://github.com/expo/eas-cli/pull/1575) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [3.0.0](https://github.com/expo/eas-cli/releases/tag/v3.0.0) - 2022-12-07
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Move update republish to separate command. ([#1533](https://github.com/expo/eas-cli/pull/1533) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Make the not recognized image error handler run only if the image is present under a valid key and has an invalid value. ([#1565](https://github.com/expo/eas-cli/pull/1565) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Fix the `node:assert` import error by using imports from `assert` instead of from `node:assert`. ([#1569](https://github.com/expo/eas-cli/pull/1569) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Remove old republish code from the update command. ([#1535](https://github.com/expo/eas-cli/pull/1535) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
## [2.9.0](https://github.com/expo/eas-cli/releases/tag/v2.9.0) - 2022-12-05
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add caching for `eas build:run` command. ([#1542](https://github.com/expo/eas-cli/pull/1542) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add `isGitWorkingTreeDirty` to EAS Update records. ([#1550](https://github.com/expo/eas-cli/pull/1550) by [@FiberJW](https://github.com/FiberJW))
|
||||
- Prompt developer to download the app after the simulator build is finished. ([#1554](https://github.com/expo/eas-cli/pull/1554) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Ask for the missing application identifier consistently in `eas build:version:sync` command. ([#1543](https://github.com/expo/eas-cli/pull/1543) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Disable selecting builds whose artifacts have expired in the `eas build:run` command. ([#1547](https://github.com/expo/eas-cli/pull/1547) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Change intent with which the `eas build:run` command opens an Android app to `android.intent.action.MAIN`. ([#1556](https://github.com/expo/eas-cli/pull/1556) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [2.8.0](https://github.com/expo/eas-cli/releases/tag/v2.8.0) - 2022-11-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas build:run` support for Android. ([#1485](https://github.com/expo/eas-cli/pull/1485) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Only create a new channel when update branch is new. ([#1507](https://github.com/expo/eas-cli/pull/1507) by [@byCedric](https://github.com/byCedric))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Better wording in eas submit. ([#1527](https://github.com/expo/eas-cli/pull/1527) by [@dsokal](https://github.com/dsokal))
|
||||
- Upload project sources for EAS Build and archives for EAS Submit to GCS. ([#1524](https://github.com/expo/eas-cli/pull/1524) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add a better error message for when an invalid `image` is set in `eas-json`. ([#1531](https://github.com/expo/eas-cli/pull/1531) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Remove internal expo id from device formatter. ([#1541](https://github.com/expo/eas-cli/pull/1541) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [2.7.1](https://github.com/expo/eas-cli/releases/tag/v2.7.1) - 2022-11-16
|
||||
|
||||
@@ -295,7 +364,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- Add new option to `eas device:create` - allow importing devices from Apple Developer Portal. ([#1236](https://github.com/expo/eas-cli/pull/1236) by [@dsokal](https://github.com/dsokal))
|
||||
- Add support for attaching messages to builds. ([#1237](https://github.com/expo/eas-cli/pull/1237) by [@dsokal](https://github.com/dsokal))
|
||||
- Introduce interactive pagination for `branch` commands. ([#1213](https://github.com/expo/eas-cli/pull/1213) by [@kgc00](https://github.com/kgc00))
|
||||
- Introduce interactive pagination for `branch` commands. ([#1213](https://github.com/expo/eas-cli/pull/1213) by [@kgc00](https://github.com/kgc00))
|
||||
|
||||
## [0.56.0](https://github.com/expo/eas-cli/releases/tag/v0.56.0) - 2022-07-28
|
||||
|
||||
|
||||
+47
-3
@@ -1,4 +1,48 @@
|
||||
## Releasing a new version
|
||||
# Development
|
||||
|
||||
1. Run `yarn release` in the repository root folder. The next version is chosen automatically based on the changelog entries. If you want to use different version, pass the version string as a positional argument to the command, e.g. `yarn release 1.2.3`.
|
||||
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
|
||||
Set up an alias for the EAS CLI so you can try it in projects all around your computer. The project is compiled on the fly so you don't need to run a build (watch) command.
|
||||
|
||||
> The only exception is when you just cloned the repository or there have been changes in `packages/eas-json`. In that case, you'll have to run `yarn build` in the root.
|
||||
|
||||
Open your `.zshrc` or other config file and add:
|
||||
|
||||
```
|
||||
alias easd="/PATH/TO/eas-cli/bin/run"
|
||||
```
|
||||
|
||||
Then use it with `easd` like `easd build`.
|
||||
|
||||
If you're making changes to `packages/eas-json` or prefer/need to work with production code, start the build command in watch mode with:
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Format
|
||||
|
||||
- Be sure to update the [`CHANGELOG.md`](./CHANGELOG.md) with changes for every PR. There is a changelog bot that can generate the proper entry for you. The instructions on how to use it are in the PR description placeholder.
|
||||
- End `async` functions with `Async` like `runAsync`. This is just how we format functions at Expo.
|
||||
- Utilize the unified `Log` module instead of `console.log`.
|
||||
|
||||
## Using local and staging API
|
||||
|
||||
For development against local API:
|
||||
|
||||
- Set up the API server as described at https://github.com/expo/universe/tree/main/server/www.
|
||||
- Set the `EXPO_LOCAL=1` env variable either on every command run (e.g. `EXPO_LOCAL=1 easd ...`) or more permamently with `export EXPO_LOCAL=1` (works in a current shell session).
|
||||
|
||||
For development against staging API:
|
||||
|
||||
- Set the `EXPO_STAGING=1` env variable either on every command run (e.g. `EXPO_STAGING=1 easd ...`) or more permamently with `export EXPO_STAGING=1` (works in a current shell session).
|
||||
|
||||
## Working on local builds (`eas build --local`)
|
||||
|
||||
See https://github.com/expo/eas-build/blob/main/DEVELOPMENT.md for how to set up your environment when making changes to [`eas-cli-local-build-plugin`](https://github.com/expo/eas-build/tree/main/packages/local-build-plugin) and/or [`build-tools`](https://github.com/expo/eas-build/tree/main/packages/build-tools).
|
||||
|
||||
## Testing
|
||||
|
||||
Run `yarn test` either in the repository root or in a package directory that you're working on.
|
||||
|
||||
## Releasing
|
||||
|
||||
See [RELEASING.md](./RELEASING.md).
|
||||
@@ -0,0 +1,14 @@
|
||||
# Releasing EAS CLI
|
||||
|
||||
1. Run `yarn release` in the repository root folder. The next version is chosen automatically based on the changelog entries. If you want to use different version, pass the version string as a positional argument to the command, e.g. `yarn release 1.2.3`.
|
||||
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
|
||||
|
||||
## Choosing the next version
|
||||
|
||||
As stated above, the next EAS CLI version is chosen automatically based on the existing changelog entries under the unreleased section in **CHANGELOG.md**.
|
||||
|
||||
The algorithm works as follows:
|
||||
|
||||
- If there are any entries in the "🛠 Breaking changes" section, bump the MAJOR version.
|
||||
- Otherwise, if there are any entries in the "🎉 New features", bump the MINOR version.
|
||||
- Otherwise, bump the PATCH version.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "2.7.1"
|
||||
"version": "3.2.0"
|
||||
}
|
||||
+136
-57
@@ -70,6 +70,8 @@ eas --help COMMAND
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:inspect`](#eas-buildinspect)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:resign`](#eas-buildresign)
|
||||
* [`eas build:run`](#eas-buildrun)
|
||||
* [`eas build:submit`](#eas-buildsubmit)
|
||||
* [`eas build:version:set`](#eas-buildversionset)
|
||||
* [`eas build:version:sync`](#eas-buildversionsync)
|
||||
@@ -106,6 +108,7 @@ eas --help COMMAND
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
* [`eas update:list`](#eas-updatelist)
|
||||
* [`eas update:republish`](#eas-updaterepublish)
|
||||
* [`eas update:view GROUPID`](#eas-updateview-groupid)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
@@ -129,7 +132,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -146,7 +149,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -163,7 +166,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -177,7 +180,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -227,7 +230,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -248,7 +251,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -268,7 +271,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -288,7 +291,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -311,7 +314,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -341,7 +344,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -358,7 +361,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -375,7 +378,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -410,7 +413,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -447,7 +450,57 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
re-sign a build archive
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:resign [-p android|ios] [-e <value>] [--wait] [--id <value>] [--offset <value>] [--limit <value>]
|
||||
[--json --non-interactive]
|
||||
|
||||
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)
|
||||
--id=<value> ID of the build to re-sign.
|
||||
--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.
|
||||
--[no-]wait Wait for build(s) to complete.
|
||||
|
||||
DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
run simulator/emulator builds from eas-cli
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:run [--latest | --id <value> | --path <value> | --url <value>] [-p android|ios] [--offset
|
||||
<value>] [--limit <value>]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios)
|
||||
--id=<value> ID of the simulator/emulator build to run
|
||||
--latest Run the latest simulator/emulator build for specified platform
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 50 and is capped at 100.
|
||||
--offset=<value> Start queries from specified index. Use for paginating results. Defaults to 0.
|
||||
--path=<value> Path to the simulator/emulator build archive or app
|
||||
--url=<value> Simulator/Emulator build archive url
|
||||
|
||||
DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -467,7 +520,7 @@ FLAGS
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=<value> Path to the .apk/.aab/.ipa file
|
||||
--url=<value> App archive url
|
||||
--verbose Always print logs from Submission Service
|
||||
--verbose Always print logs from EAS Submit
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
DESCRIPTION
|
||||
@@ -494,7 +547,7 @@ DESCRIPTION
|
||||
Update version of an app.
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -513,7 +566,7 @@ DESCRIPTION
|
||||
Update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -530,7 +583,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -551,7 +604,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -573,7 +626,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -593,7 +646,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -616,7 +669,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -635,7 +688,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -652,7 +705,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/credentials.ts)_
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -666,7 +719,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.7.1/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -686,7 +739,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -707,7 +760,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -721,7 +774,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -735,7 +788,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -823,7 +876,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -840,7 +893,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -857,7 +910,7 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas open`
|
||||
|
||||
@@ -871,7 +924,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -885,7 +938,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -907,7 +960,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas run`
|
||||
|
||||
@@ -931,7 +984,7 @@ DESCRIPTION
|
||||
run simulator build
|
||||
```
|
||||
|
||||
_See code: [src/commands/run/index.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/run/index.ts)_
|
||||
_See code: [src/commands/run/index.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/run/index.ts)_
|
||||
|
||||
## `eas run:run`
|
||||
|
||||
@@ -955,7 +1008,7 @@ DESCRIPTION
|
||||
run simulator build
|
||||
```
|
||||
|
||||
_See code: [src/commands/run/run.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/run/run.ts)_
|
||||
_See code: [src/commands/run/run.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/run/run.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -978,7 +1031,7 @@ DESCRIPTION
|
||||
create an environment secret on the current project or owner account
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -996,7 +1049,7 @@ DESCRIPTION
|
||||
delete an environment secret by ID
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -1010,7 +1063,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.7.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas secret:push`
|
||||
|
||||
@@ -1030,7 +1083,7 @@ DESCRIPTION
|
||||
read environment secrets from env file and store on the server
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1050,7 +1103,7 @@ FLAGS
|
||||
--non-interactive Run command in non-interactive mode
|
||||
--path=<value> Path to the .apk/.aab/.ipa file
|
||||
--url=<value> App archive url
|
||||
--verbose Always print logs from Submission Service
|
||||
--verbose Always print logs from EAS Submit
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
DESCRIPTION
|
||||
@@ -1060,7 +1113,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1068,14 +1121,16 @@ 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]
|
||||
$ eas update [--branch <value>] [--channel <value>] [--message <value>] [--republish | --input-dir <value>
|
||||
| --skip-bundler] [--group <value> | | ] [-p android|ios|all] [--auto] [--private-key-path <value>] [--json
|
||||
--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--group=<value> Update group to republish
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--group=<value> Update group to republish (deprecated, see republish command)
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--message=<value> A short message describing the update
|
||||
@@ -1083,14 +1138,14 @@ FLAGS
|
||||
--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.
|
||||
--republish Republish an update group
|
||||
--republish Republish an update group (deprecated, see republish command)
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
|
||||
DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1108,7 +1163,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1129,7 +1184,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1151,7 +1206,31 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
rollback to an existing update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:republish [--channel <value> | --branch <value> | --group <value>] [--message <value>] [-p
|
||||
android|ios|all] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--branch=<value> Branch name to select an update to republish from
|
||||
--channel=<value> Channel name to select an update to republish from
|
||||
--group=<value> Update group ID to republish
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--message=<value> Short message describing the republished update
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
rollback to an existing update
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1171,7 +1250,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1192,7 +1271,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1212,7 +1291,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1229,7 +1308,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1251,7 +1330,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1268,7 +1347,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v2.7.1/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v3.2.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
|
||||
Generated
+656
-56
@@ -2258,22 +2258,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "redirectUri",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "revokeEndpoint",
|
||||
"description": "",
|
||||
@@ -2438,22 +2422,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "redirectUri",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "revokeEndpoint",
|
||||
"description": "",
|
||||
@@ -2742,22 +2710,6 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "redirectUri",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "revokeEndpoint",
|
||||
"description": "",
|
||||
@@ -5129,6 +5081,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "projectArchive",
|
||||
"description": "",
|
||||
@@ -5435,6 +5399,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "releaseChannel",
|
||||
"description": "",
|
||||
@@ -6623,6 +6599,51 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "deploymentNew",
|
||||
"description": "",
|
||||
"args": [
|
||||
{
|
||||
"name": "channel",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "runtimeVersion",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentNew",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "deployments",
|
||||
"description": "Deployments associated with this app",
|
||||
@@ -12012,7 +12033,20 @@
|
||||
{
|
||||
"name": "canRetry",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"args": [
|
||||
{
|
||||
"name": "newMode",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
@@ -13860,6 +13894,29 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "BUILD",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "RESIGN",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BuildMutation",
|
||||
@@ -14713,6 +14770,29 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "BuildResignInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "applicationArchiveSource",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "ProjectArchiveSourceInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "BuildResourceClass",
|
||||
@@ -14849,6 +14929,12 @@
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "UNKNOWN",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
@@ -16499,6 +16585,224 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentBuildEdge",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Build",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentBuildsConnection",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentBuildEdge",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentNew",
|
||||
"description": "Represents a Deployment - a set of Builds with the same Runtime Version and Channel",
|
||||
"fields": [
|
||||
{
|
||||
"name": "builds",
|
||||
"description": "",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DeploymentBuildsConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "UpdateChannel",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "runtime",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Runtime",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DeploymentOptions",
|
||||
@@ -17580,13 +17884,9 @@
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
@@ -20750,6 +21050,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "projectArchive",
|
||||
"description": "",
|
||||
@@ -21021,6 +21333,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildMode",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "releaseChannel",
|
||||
"description": "",
|
||||
@@ -21033,6 +21357,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "resign",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "BuildResignInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "scheme",
|
||||
"description": "",
|
||||
@@ -21069,6 +21405,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "BuildWorkflow",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updates",
|
||||
"description": "",
|
||||
@@ -22479,6 +22827,73 @@
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "endCursor",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "hasNextPage",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "hasPreviousPage",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "startCursor",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "PartialManifest",
|
||||
@@ -22938,6 +23353,12 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "NONE",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "S3",
|
||||
"description": "",
|
||||
@@ -23267,6 +23688,26 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "excludedAssets",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "PartialManifestAsset",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "gitCommitHash",
|
||||
"description": "",
|
||||
@@ -23279,6 +23720,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isGitWorkingTreeDirty",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"description": "",
|
||||
@@ -25098,6 +25551,81 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Runtime",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "app",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "App",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "firstBuildCreatedAt",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "SecondFactorBooleanResult",
|
||||
@@ -27659,6 +28187,22 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isGitWorkingTreeDirty",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "manifestFragment",
|
||||
"description": "",
|
||||
@@ -30000,6 +30544,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "accountProfilePhoto",
|
||||
"description": "If the invite is for a personal team, the profile photo of account owner",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"description": "",
|
||||
@@ -30064,6 +30620,22 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isForOrganization",
|
||||
"description": "If the invite is for an organization or a personal team",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"description": "Account permissions to be granted upon acceptance of this invitation",
|
||||
@@ -30376,6 +30948,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "accountProfilePhoto",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"description": "",
|
||||
@@ -30439,6 +31023,22 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isForOrganization",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "2.7.1",
|
||||
"version": "3.2.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.97",
|
||||
"@expo/eas-json": "2.7.1",
|
||||
"@expo/eas-json": "3.0.0",
|
||||
"@expo/json-file": "8.2.36",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/osascript": "2.0.33",
|
||||
|
||||
@@ -15,6 +15,7 @@ import Log from '../log';
|
||||
import { formatBranch, getBranchDescription } from '../update/utils';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import {
|
||||
SelectPromptEntry,
|
||||
paginatedQueryWithConfirmPromptAsync,
|
||||
paginatedQueryWithSelectPromptAsync,
|
||||
} from '../utils/queries';
|
||||
@@ -31,7 +32,7 @@ export async function selectBranchOnAppAsync(
|
||||
paginatedQueryOptions,
|
||||
}: {
|
||||
projectId: string;
|
||||
displayTextForListItem: (queryItem: UpdateBranchFragment) => string;
|
||||
displayTextForListItem: (queryItem: UpdateBranchFragment) => SelectPromptEntry;
|
||||
promptTitle: string;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
}
|
||||
@@ -48,7 +49,7 @@ export async function selectBranchOnAppAsync(
|
||||
promptOptions: {
|
||||
title: promptTitle,
|
||||
getIdentifierForQueryItem: updateBranchFragment => updateBranchFragment.id,
|
||||
createDisplayTextForSelectionPromptListItem: displayTextForListItem,
|
||||
makePartialChoiceObject: displayTextForListItem,
|
||||
},
|
||||
});
|
||||
if (!selectedBranch) {
|
||||
@@ -160,7 +161,7 @@ export async function ensureBranchExistsAsync(
|
||||
appId: string;
|
||||
branchName: string;
|
||||
}
|
||||
): Promise<{ branchId: string }> {
|
||||
): Promise<{ branchId: string; createdBranch: boolean }> {
|
||||
try {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
appId,
|
||||
@@ -168,14 +169,14 @@ export async function ensureBranchExistsAsync(
|
||||
});
|
||||
|
||||
const { id } = updateBranch;
|
||||
return { branchId: id };
|
||||
return { branchId: id, createdBranch: false };
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
return { branchId: newUpdateBranch.id };
|
||||
return { branchId: newUpdateBranch.id, createdBranch: true };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
);
|
||||
}
|
||||
|
||||
type MaybeBuildFragment = BuildFragment | null;
|
||||
export type MaybeBuildFragment = BuildFragment | null;
|
||||
|
||||
export async function waitForBuildEndAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
@@ -362,7 +362,7 @@ async function handleSingleBuildProgressAsync(
|
||||
spinner.succeed('Build finished');
|
||||
return { refetch: false };
|
||||
case BuildStatus.New:
|
||||
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${chalk.underline(
|
||||
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${link(
|
||||
formatAccountSubscriptionsUrl(accountName)
|
||||
)}.`;
|
||||
break;
|
||||
@@ -383,7 +383,7 @@ async function handleSingleBuildProgressAsync(
|
||||
Log.newLine();
|
||||
Log.log('Start builds sooner in the priority queue.');
|
||||
Log.log(
|
||||
`Sign up for EAS Production or Enterprise at ${chalk.underline(
|
||||
`Sign up for EAS Production or Enterprise at ${link(
|
||||
formatAccountSubscriptionsUrl(accountName)
|
||||
)}`
|
||||
);
|
||||
@@ -423,8 +423,8 @@ async function handleSingleBuildProgressAsync(
|
||||
}
|
||||
|
||||
const priorityToQueueDisplayName: Record<BuildPriority, string> = {
|
||||
[BuildPriority.Normal]: 'queue',
|
||||
[BuildPriority.NormalPlus]: 'queue',
|
||||
[BuildPriority.Normal]: 'Free tier queue',
|
||||
[BuildPriority.NormalPlus]: 'Free tier queue',
|
||||
[BuildPriority.High]: 'priority queue',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { BuildProfile } from '@expo/eas-json';
|
||||
|
||||
import { CredentialsContext } from '../../credentials/context';
|
||||
import IosCredentialsProvider from '../../credentials/ios/IosCredentialsProvider';
|
||||
import { getAppFromContextAsync } from '../../credentials/ios/actions/BuildCredentialsUtils';
|
||||
import { IosCredentials, Target } from '../../credentials/ios/types';
|
||||
@@ -31,6 +33,27 @@ export async function ensureIosCredentialsAsync(
|
||||
};
|
||||
}
|
||||
|
||||
export async function ensureIosCredentialsForBuildResignAsync(
|
||||
credentialsCtx: CredentialsContext,
|
||||
targets: Target[],
|
||||
buildProfile: BuildProfile<Platform.IOS>
|
||||
): Promise<CredentialsResult<IosCredentials>> {
|
||||
const provider = new IosCredentialsProvider(credentialsCtx, {
|
||||
app: await getAppFromContextAsync(credentialsCtx),
|
||||
targets,
|
||||
distribution: 'internal',
|
||||
enterpriseProvisioning: buildProfile.enterpriseProvisioning,
|
||||
});
|
||||
|
||||
const { credentialsSource } = buildProfile;
|
||||
|
||||
logCredentialsSource(credentialsSource, Platform.IOS);
|
||||
return {
|
||||
credentials: await provider.getCredentialsAsync(credentialsSource),
|
||||
source: credentialsSource,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldProvideCredentials(buildCtx: BuildContext<Platform.IOS>): boolean {
|
||||
return !buildCtx.buildProfile.simulator;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
};
|
||||
}
|
||||
|
||||
function transformIosSecrets(secrets: {
|
||||
export function transformIosSecrets(secrets: {
|
||||
buildCredentials?: Ios.BuildCredentials;
|
||||
}): IosJobSecretsInput {
|
||||
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ArchiveSource, Ios, Job, Platform, sanitizeJob } from '@expo/eas-build-job';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { IosJobSecretsInput } from '../../graphql/generated';
|
||||
import { getUsername } from '../../project/projectUtils';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
import { BuildContext } from '../context';
|
||||
@@ -79,6 +81,22 @@ export async function prepareJobAsync(
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
|
||||
export function prepareCredentialsToResign(credentials: IosCredentials): IosJobSecretsInput {
|
||||
const buildCredentials: IosJobSecretsInput['buildCredentials'] = [];
|
||||
for (const targetName of Object.keys(credentials ?? {})) {
|
||||
buildCredentials.push({
|
||||
targetName,
|
||||
provisioningProfileBase64: nullthrows(credentials?.[targetName].provisioningProfile),
|
||||
distributionCertificate: {
|
||||
dataBase64: nullthrows(credentials?.[targetName].distributionCertificate.certificateP12),
|
||||
password: nullthrows(credentials?.[targetName].distributionCertificate.certificatePassword),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { buildCredentials };
|
||||
}
|
||||
|
||||
function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.TargetCredentials {
|
||||
return {
|
||||
provisioningProfileBase64: targetCredentials.provisioningProfile,
|
||||
|
||||
@@ -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.119';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.120';
|
||||
|
||||
export interface LocalBuildOptions {
|
||||
enable: boolean;
|
||||
|
||||
@@ -2,13 +2,15 @@ import chalk from 'chalk';
|
||||
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { PaginatedQueryOptions } from '../commandUtils/pagination';
|
||||
import { BuildFilter, BuildFragment } from '../graphql/generated';
|
||||
import { AppPlatform, BuildFilter, BuildFragment } from '../graphql/generated';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
import Log from '../log';
|
||||
import { appPlatformDisplayNames } from '../platform';
|
||||
import { promptAsync } from '../prompts';
|
||||
import { fromNow } from '../utils/date';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import { paginatedQueryWithConfirmPromptAsync } from '../utils/queries';
|
||||
import {
|
||||
paginatedQueryWithConfirmPromptAsync,
|
||||
paginatedQueryWithSelectPromptAsync,
|
||||
} from '../utils/queries';
|
||||
import { formatGraphQLBuild } from './utils/formatBuild';
|
||||
|
||||
export const BUILDS_LIMIT = 50;
|
||||
@@ -55,43 +57,83 @@ export async function listAndRenderBuildsOnAppAsync(
|
||||
}
|
||||
}
|
||||
|
||||
export async function listAndSelectBuildsOnAppAsync(
|
||||
export async function listAndSelectBuildOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
projectDisplayName,
|
||||
title,
|
||||
filter,
|
||||
queryOptions,
|
||||
paginatedQueryOptions,
|
||||
selectPromptDisabledFunction,
|
||||
selectPromptWarningMessage,
|
||||
}: {
|
||||
projectId: string;
|
||||
projectDisplayName: string;
|
||||
title: string;
|
||||
filter?: BuildFilter;
|
||||
queryOptions: PaginatedQueryOptions;
|
||||
paginatedQueryOptions: PaginatedQueryOptions;
|
||||
selectPromptDisabledFunction?: (build: BuildFragment) => boolean;
|
||||
selectPromptWarningMessage?: string;
|
||||
}
|
||||
): Promise<BuildFragment> {
|
||||
const builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
limit: queryOptions.limit ?? BUILDS_LIMIT,
|
||||
offset: queryOptions.offset,
|
||||
filter,
|
||||
});
|
||||
|
||||
if (builds.length === 0) {
|
||||
throw new Error('Found no builds matching the provided criteria.');
|
||||
): Promise<BuildFragment | void> {
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
throw new Error('Unable to select a build in non-interactive mode.');
|
||||
} else {
|
||||
return await paginatedQueryWithSelectPromptAsync({
|
||||
limit: paginatedQueryOptions.limit ?? BUILDS_LIMIT,
|
||||
offset: paginatedQueryOptions.offset,
|
||||
queryToPerform: (limit, offset) =>
|
||||
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
limit,
|
||||
offset,
|
||||
filter,
|
||||
}),
|
||||
promptOptions: {
|
||||
title,
|
||||
getIdentifierForQueryItem: build => build.id,
|
||||
makePartialChoiceObject: createBuildToPartialChoiceMaker(selectPromptDisabledFunction),
|
||||
selectPromptWarningMessage,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { selectedSimulatorBuild } = await promptAsync({
|
||||
type: 'select',
|
||||
message: `Select simulator build to run for ${projectDisplayName} app`,
|
||||
name: 'selectedSimulatorBuild',
|
||||
choices: builds.map(build => ({
|
||||
title: `id: ${build.id}, platform: ${appPlatformDisplayNames[build.platform]}, version: ${
|
||||
build.appVersion
|
||||
}, build number: ${build.appBuildVersion}`,
|
||||
value: build,
|
||||
})),
|
||||
});
|
||||
return selectedSimulatorBuild;
|
||||
function formatBuildChoiceValue(value: string | undefined | null): string {
|
||||
return value ? chalk.bold(value) : 'Unknown';
|
||||
}
|
||||
|
||||
function createBuildToPartialChoiceMaker(
|
||||
selectPromptDisabledFunction?: (build: BuildFragment) => boolean
|
||||
) {
|
||||
return (
|
||||
build: BuildFragment
|
||||
): {
|
||||
title: string;
|
||||
description: string;
|
||||
disabled?: boolean;
|
||||
} => {
|
||||
const splitCommitMessage = build.gitCommitMessage?.split('\n');
|
||||
const formattedCommitData =
|
||||
build.gitCommitHash && splitCommitMessage && splitCommitMessage.length > 0
|
||||
? `${build.gitCommitHash.slice(0, 7)} "${chalk.bold(
|
||||
splitCommitMessage[0] + (splitCommitMessage.length > 1 ? '…' : '')
|
||||
)}"`
|
||||
: 'Unknown';
|
||||
|
||||
return {
|
||||
title: `${chalk.bold(`ID:`)} ${build.id} (${chalk.bold(
|
||||
`${fromNow(new Date(build.completedAt))} ago`
|
||||
)})`,
|
||||
description: [
|
||||
`\t${chalk.bold(`Version:`)} ${formatBuildChoiceValue(build.appVersion)}`,
|
||||
`\t${chalk.bold(
|
||||
build.platform === AppPlatform.Ios ? 'Build number:' : 'Version code:'
|
||||
)} ${formatBuildChoiceValue(build.appBuildVersion)}`,
|
||||
`\t${chalk.bold(`Commit:`)} ${formattedCommitData}`,
|
||||
].join('\n'),
|
||||
disabled: selectPromptDisabledFunction?.(build),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function getLatestBuildAsync(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
EasJsonUtils,
|
||||
SubmitProfile,
|
||||
} from '@expo/eas-json';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
@@ -37,6 +38,9 @@ import {
|
||||
validateAppConfigForRemoteVersionSource,
|
||||
validateBuildProfileVersionSettings,
|
||||
} from '../project/remoteVersionSource';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { runAsync } from '../run/run';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../run/utils';
|
||||
import { createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish,
|
||||
@@ -46,11 +50,13 @@ import {
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
|
||||
import { Actor } from '../user/User';
|
||||
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
|
||||
import { truthy } from '../utils/expodash/filter';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
import { BuildRequestSender, waitForBuildEndAsync } from './build';
|
||||
import { BuildRequestSender, MaybeBuildFragment, waitForBuildEndAsync } from './build';
|
||||
import { ensureProjectConfiguredAsync } from './configure';
|
||||
import { BuildContext } from './context';
|
||||
import { createBuildContextAsync } from './createContext';
|
||||
@@ -242,6 +248,9 @@ export async function runBuildAndSubmitAsync(
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
|
||||
);
|
||||
|
||||
await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags);
|
||||
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(builds);
|
||||
@@ -409,3 +418,35 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | nul
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndRunAsync(build: BuildFragment): Promise<void> {
|
||||
assert(build.artifacts?.applicationArchiveUrl);
|
||||
const buildPath = await downloadAndMaybeExtractAppAsync(
|
||||
build.artifacts.applicationArchiveUrl,
|
||||
build.platform
|
||||
);
|
||||
await runAsync(buildPath, build.platform);
|
||||
}
|
||||
|
||||
async function maybeDownloadAndRunSimulatorBuildsAsync(
|
||||
builds: MaybeBuildFragment[],
|
||||
flags: BuildFlags
|
||||
): Promise<void> {
|
||||
const simBuilds = builds.filter(truthy).filter(isRunnableOnSimulatorOrEmulator);
|
||||
|
||||
if (simBuilds.length > 0 && !flags.autoSubmit && !flags.nonInteractive) {
|
||||
for (const simBuild of simBuilds) {
|
||||
if (simBuild.platform === AppPlatform.Android || process.platform === 'darwin') {
|
||||
Log.newLine();
|
||||
const confirm = await confirmAsync({
|
||||
message: `Install and run the ${
|
||||
simBuild.platform === AppPlatform.Android ? 'Android' : 'iOS'
|
||||
} build on ${simBuild.platform === AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
|
||||
});
|
||||
if (confirm) {
|
||||
await downloadAndRunAsync(simBuild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import indentString from 'indent-string';
|
||||
import qrcodeTerminal from 'qrcode-terminal';
|
||||
|
||||
@@ -12,20 +11,20 @@ import {
|
||||
EasBuildDeprecationInfo,
|
||||
EasBuildDeprecationInfoType,
|
||||
} from '../../graphql/generated';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import { appPlatformDisplayNames, appPlatformEmojis } from '../../platform';
|
||||
import { getBuildLogsUrl, getInternalDistributionInstallUrl } from './url';
|
||||
|
||||
export function printLogsUrls(builds: BuildFragment[]): void {
|
||||
if (builds.length === 1) {
|
||||
Log.log(`Build details: ${chalk.underline(getBuildLogsUrl(builds[0]))}`);
|
||||
Log.log(`Build details: ${link(getBuildLogsUrl(builds[0]))}`);
|
||||
} else {
|
||||
builds.forEach(build => {
|
||||
const logsUrl = getBuildLogsUrl(build);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
} build details: ${chalk.underline(logsUrl)}`
|
||||
} build details: ${link(logsUrl)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -79,13 +78,13 @@ function printBuildResult(build: BuildFragment): void {
|
||||
appPlatformDisplayNames[build.platform]
|
||||
} devices (or scan the QR code) to install the app:`
|
||||
);
|
||||
Log.log(`${chalk.underline(logsUrl)}`);
|
||||
Log.log(`${link(logsUrl)}`);
|
||||
} else {
|
||||
// TODO: it looks like buildUrl could possibly be undefined, based on the code below.
|
||||
// we should account for this case better if it is possible
|
||||
const url = build.artifacts?.buildUrl ?? '';
|
||||
Log.log(`${appPlatformEmojis[build.platform]} ${appPlatformDisplayNames[build.platform]} app:`);
|
||||
Log.log(`${chalk.underline(url)}`);
|
||||
Log.log(`${link(url)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export class ChannelNotFoundError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message ?? 'Channel not found.');
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export async function selectChannelOnAppAsync(
|
||||
queryChannelsOnAppAsync(graphqlClient, { appId: projectId, limit, offset }),
|
||||
promptOptions: {
|
||||
title: selectionPromptTitle,
|
||||
createDisplayTextForSelectionPromptListItem: updateChannel => updateChannel.name,
|
||||
makePartialChoiceObject: updateChannel => ({ title: updateChannel.name }),
|
||||
getIdentifierForQueryItem: updateChannel => updateChannel.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ describe(EasCommand.name, () => {
|
||||
|
||||
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
||||
expect(sessionManagerSpy).toBeCalledTimes(1);
|
||||
}, 15_000);
|
||||
}, 30_000);
|
||||
|
||||
it('initializes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class BranchDelete extends EasCommand {
|
||||
}
|
||||
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
displayTextForListItem: updateBranch => ({ title: updateBranch.name }),
|
||||
promptTitle: 'Which branch would you like to delete?',
|
||||
paginatedQueryOptions,
|
||||
}));
|
||||
|
||||
@@ -57,7 +57,9 @@ export default class BranchView extends EasCommand {
|
||||
({ name: branchName } = await selectBranchOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
promptTitle: 'Which branch would you like to view?',
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
displayTextForListItem: updateBranch => ({
|
||||
title: updateBranch.name,
|
||||
}),
|
||||
// discard limit and offset because this query is not their intended target
|
||||
paginatedQueryOptions: {
|
||||
json: paginatedQueryOptions.json,
|
||||
|
||||
@@ -245,7 +245,7 @@ export default class Build extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeprecatedEasJsonAsync(
|
||||
export async function handleDeprecatedEasJsonAsync(
|
||||
projectDir: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { CredentialsSource, EasJson, EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { Flags } from '@oclif/core';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { handleDeprecatedEasJsonAsync } from '.';
|
||||
import { waitForBuildEndAsync } from '../../build/build';
|
||||
import { ensureIosCredentialsForBuildResignAsync } from '../../build/ios/credentials';
|
||||
import { prepareCredentialsToResign } from '../../build/ios/prepareJob';
|
||||
import { listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { EasPaginatedQueryFlags } from '../../commandUtils/pagination';
|
||||
import { CredentialsContext } from '../../credentials/context';
|
||||
import {
|
||||
BuildFragment,
|
||||
BuildMode,
|
||||
BuildStatus,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
IosJobOverridesInput,
|
||||
ProjectArchiveSourceType,
|
||||
StatuspageServiceName,
|
||||
} from '../../graphql/generated';
|
||||
import { BuildMutation } from '../../graphql/mutations/BuildMutation';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import { toAppPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log from '../../log';
|
||||
import { requestedPlatformDisplayNames, selectPlatformAsync } from '../../platform';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
|
||||
|
||||
interface BuildResignFlags {
|
||||
json: boolean;
|
||||
nonInteractive: boolean;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
platform?: Platform;
|
||||
profile?: string;
|
||||
maybeBuildId?: string;
|
||||
wait: boolean;
|
||||
}
|
||||
interface RawBuildResignFlags {
|
||||
json: boolean;
|
||||
'non-interactive': boolean;
|
||||
offset: number | undefined;
|
||||
limit: number | undefined;
|
||||
platform: 'android' | 'ios' | undefined;
|
||||
profile: string | undefined;
|
||||
wait: boolean;
|
||||
id: string | undefined;
|
||||
}
|
||||
|
||||
export default class BuildResign extends EasCommand {
|
||||
static override description = 're-sign a build archive';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: ['android', 'ios'],
|
||||
}),
|
||||
profile: Flags.string({
|
||||
char: 'e',
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
wait: Flags.boolean({
|
||||
default: true,
|
||||
allowNo: true,
|
||||
description: 'Wait for build(s) to complete.',
|
||||
}),
|
||||
id: Flags.string({
|
||||
description: 'ID of the build to re-sign.',
|
||||
}),
|
||||
...EasPaginatedQueryFlags,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.Analytics,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(BuildResign);
|
||||
if (rawFlags.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const flags = this.sanitizeFlags(rawFlags as RawBuildResignFlags);
|
||||
const { limit, offset, nonInteractive } = flags;
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
getDynamicProjectConfigAsync,
|
||||
projectDir,
|
||||
analytics,
|
||||
} = await this.getContextAsync(BuildResign, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
const maybeBuild = flags.maybeBuildId
|
||||
? await this.maybeGetBuildAsync(graphqlClient, flags.maybeBuildId)
|
||||
: undefined;
|
||||
const platform =
|
||||
(maybeBuild?.platform.toLowerCase() as Platform | undefined) ??
|
||||
(await selectPlatformAsync(flags.platform));
|
||||
if (platform === Platform.ANDROID) {
|
||||
throw new Error('Re-signing archives is not supported on Android yet.');
|
||||
}
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasBuild]);
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
const easJsonCliConfig: EasJson['cli'] =
|
||||
(await EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {};
|
||||
|
||||
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
easJsonAccessor,
|
||||
platform,
|
||||
flags.profile ?? 'production'
|
||||
);
|
||||
const { exp, projectId } = await getDynamicProjectConfigAsync({ env: buildProfile.env });
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const build = await this.ensureBuildSelectedAsync(
|
||||
{ graphqlClient, projectId, platform, nonInteractive, limit, offset },
|
||||
maybeBuild
|
||||
);
|
||||
const credentialsCtx = new CredentialsContext({
|
||||
projectInfo: { exp, projectId },
|
||||
nonInteractive,
|
||||
projectDir,
|
||||
user: actor,
|
||||
graphqlClient,
|
||||
analytics,
|
||||
env: buildProfile.env,
|
||||
easJsonCliConfig,
|
||||
});
|
||||
if (buildProfile.credentialsSource !== CredentialsSource.LOCAL && !nonInteractive) {
|
||||
await credentialsCtx.appStore.ensureAuthenticatedAsync();
|
||||
}
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{
|
||||
projectDir,
|
||||
nonInteractive,
|
||||
exp,
|
||||
},
|
||||
buildProfile
|
||||
);
|
||||
const targets = await resolveTargetsAsync({
|
||||
projectDir,
|
||||
exp,
|
||||
xcodeBuildContext,
|
||||
env: buildProfile.env,
|
||||
});
|
||||
const credentialsResult = await ensureIosCredentialsForBuildResignAsync(
|
||||
credentialsCtx,
|
||||
targets,
|
||||
buildProfile
|
||||
);
|
||||
assert(build.artifacts?.applicationArchiveUrl, 'Missing application archive.');
|
||||
const jobOverrides: IosJobOverridesInput = {
|
||||
mode: BuildMode.Resign,
|
||||
type: BuildWorkflow.Unknown,
|
||||
resign: {
|
||||
applicationArchiveSource: {
|
||||
type: ProjectArchiveSourceType.Url,
|
||||
url: build.artifacts?.applicationArchiveUrl,
|
||||
},
|
||||
},
|
||||
secrets: prepareCredentialsToResign(credentialsResult.credentials),
|
||||
builderEnvironment: { image: 'default' },
|
||||
};
|
||||
const newBuild = await BuildMutation.retryIosBuildAsync(graphqlClient, {
|
||||
buildId: build.id,
|
||||
jobOverrides,
|
||||
});
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
printLogsUrls([newBuild]);
|
||||
Log.newLine();
|
||||
if (!flags.wait) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(newBuild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const buildResult = await waitForBuildEndAsync(graphqlClient, {
|
||||
buildIds: [newBuild.id],
|
||||
accountName: account.name,
|
||||
});
|
||||
if (!flags.json) {
|
||||
printBuildResults(buildResult);
|
||||
} else {
|
||||
assert(buildResult[0], 'missing build results');
|
||||
printJsonOnlyOutput(buildResult[0]);
|
||||
}
|
||||
}
|
||||
|
||||
sanitizeFlags(flags: RawBuildResignFlags): BuildResignFlags {
|
||||
const nonInteractive = flags['non-interactive'];
|
||||
if (nonInteractive && !flags.id) {
|
||||
throw new Error(
|
||||
`${chalk.bold('--id')} is required when running with ${chalk.bold(
|
||||
'--non-interactive'
|
||||
)} flag.`
|
||||
);
|
||||
}
|
||||
return {
|
||||
json: flags.json,
|
||||
nonInteractive,
|
||||
offset: flags.offset,
|
||||
limit: flags.limit,
|
||||
platform: flags.platform as Platform | undefined,
|
||||
profile: flags.profile,
|
||||
maybeBuildId: flags.id,
|
||||
wait: flags.wait,
|
||||
};
|
||||
}
|
||||
|
||||
async ensureBuildSelectedAsync(
|
||||
{
|
||||
graphqlClient,
|
||||
projectId,
|
||||
platform,
|
||||
nonInteractive,
|
||||
limit,
|
||||
offset,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
platform: Platform;
|
||||
nonInteractive: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
maybeBuild?: BuildFragment
|
||||
): Promise<BuildFragment> {
|
||||
if (maybeBuild) {
|
||||
return maybeBuild;
|
||||
}
|
||||
const build = await listAndSelectBuildOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
title: 'Which build would you like to re-sign with new credentials?',
|
||||
paginatedQueryOptions: {
|
||||
limit,
|
||||
offset: offset ?? 0,
|
||||
nonInteractive,
|
||||
json: false,
|
||||
},
|
||||
filter: {
|
||||
distribution: DistributionType.Internal,
|
||||
platform: toAppPlatform(platform),
|
||||
status: BuildStatus.Finished,
|
||||
},
|
||||
});
|
||||
if (!build) {
|
||||
throw new Error('There are no builds that can be re-signed on this project.');
|
||||
}
|
||||
return build;
|
||||
}
|
||||
|
||||
async maybeGetBuildAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
maybeBuildId?: string,
|
||||
maybePlatform?: Platform
|
||||
): Promise<BuildFragment | undefined> {
|
||||
if (maybeBuildId) {
|
||||
const build = await BuildQuery.byIdAsync(graphqlClient, maybeBuildId);
|
||||
if (build.distribution !== DistributionType.Internal) {
|
||||
throw new Error('This is not an internal distribution build.');
|
||||
}
|
||||
if (build.status !== BuildStatus.Finished) {
|
||||
throw new Error('Only builds that finished successfully can be re-signed.');
|
||||
}
|
||||
if (maybePlatform && build.platform !== toAppPlatform(maybePlatform)) {
|
||||
throw new Error(
|
||||
`Build with ID ${maybeBuildId} was not created for platform ${requestedPlatformDisplayNames[maybePlatform]}.`
|
||||
);
|
||||
}
|
||||
return build;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import { existsSync } from 'fs-extra';
|
||||
import assert from 'node:assert';
|
||||
import assert from 'assert';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { getLatestBuildAsync, listAndSelectBuildsOnAppAsync } from '../../build/queries';
|
||||
import { BuildDistributionType } from '../../build/types';
|
||||
import { getLatestBuildAsync, listAndSelectBuildOnAppAsync } from '../../build/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
@@ -11,13 +11,18 @@ import {
|
||||
PaginatedQueryOptions,
|
||||
getPaginatedQueryOptions,
|
||||
} from '../../commandUtils/pagination';
|
||||
import { AppPlatform, BuildFragment, BuildStatus } from '../../graphql/generated';
|
||||
import { AppPlatform, BuildFragment, BuildStatus, DistributionType } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { RunArchiveFlags, runAsync } from '../../run/run';
|
||||
import { buildDistributionTypeToGraphQLDistributionType } from '../../utils/buildDistribution';
|
||||
import { downloadAndExtractAppAsync, extractAppFromLocalArchiveAsync } from '../../utils/download';
|
||||
import { isRunnableOnSimulatorOrEmulator } from '../../run/utils';
|
||||
import {
|
||||
downloadAndMaybeExtractAppAsync,
|
||||
extractAppFromLocalArchiveAsync,
|
||||
} from '../../utils/download';
|
||||
import { getEasBuildRunCacheDirectoryPath } from '../../utils/paths';
|
||||
|
||||
interface RawRunFlags {
|
||||
latest?: boolean;
|
||||
@@ -37,8 +42,6 @@ interface RunCommandFlags {
|
||||
}
|
||||
|
||||
export default class Run extends EasCommand {
|
||||
static override hidden = true;
|
||||
|
||||
static override description = 'run simulator/emulator builds from eas-cli';
|
||||
|
||||
static override flags = {
|
||||
@@ -51,7 +54,7 @@ export default class Run extends EasCommand {
|
||||
exclusive: ['latest', 'id', 'path'],
|
||||
}),
|
||||
path: Flags.string({
|
||||
description: 'Path to the simulator/emulator build archive or simulator build app',
|
||||
description: 'Path to the simulator/emulator build archive or app',
|
||||
exclusive: ['latest', 'id', 'url'],
|
||||
}),
|
||||
id: Flags.string({
|
||||
@@ -97,13 +100,19 @@ export default class Run extends EasCommand {
|
||||
|
||||
const selectedPlatform = await resolvePlatformAsync(platform);
|
||||
|
||||
if (platform === 'ios' && process.platform !== 'darwin') {
|
||||
Errors.error('You can only use an iOS simulator to run apps on macOS devices', {
|
||||
exit: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
runArchiveFlags.path &&
|
||||
!(
|
||||
(runArchiveFlags.path.endsWith('.tar.gz') ||
|
||||
runArchiveFlags.path.endsWith('.app') ||
|
||||
runArchiveFlags.path.endsWith('.apk')) &&
|
||||
existsSync(runArchiveFlags.path)
|
||||
(await pathExists(runArchiveFlags.path))
|
||||
)
|
||||
) {
|
||||
Errors.error('The path must point to a .tar.gz archive, .apk file, or .app directory', {
|
||||
@@ -121,6 +130,10 @@ export default class Run extends EasCommand {
|
||||
}
|
||||
|
||||
async function resolvePlatformAsync(platform?: string): Promise<AppPlatform> {
|
||||
if (process.platform !== 'darwin') {
|
||||
return AppPlatform.Android;
|
||||
}
|
||||
|
||||
if (platform && Object.values(AppPlatform).includes(platform.toUpperCase() as AppPlatform)) {
|
||||
return platform.toUpperCase() as AppPlatform;
|
||||
}
|
||||
@@ -143,6 +156,9 @@ async function maybeGetBuildAsync(
|
||||
projectId: string,
|
||||
paginatedQueryOptions: PaginatedQueryOptions
|
||||
): Promise<BuildFragment | null> {
|
||||
const distributionType =
|
||||
flags.selectedPlatform === AppPlatform.Ios ? DistributionType.Simulator : undefined;
|
||||
|
||||
if (flags.runArchiveFlags.id) {
|
||||
return BuildQuery.byIdAsync(graphqlClient, flags.runArchiveFlags.id);
|
||||
} else if (
|
||||
@@ -151,26 +167,29 @@ async function maybeGetBuildAsync(
|
||||
!flags.runArchiveFlags.url &&
|
||||
!flags.runArchiveFlags.latest
|
||||
) {
|
||||
return await listAndSelectBuildsOnAppAsync(graphqlClient, {
|
||||
const build = await listAndSelectBuildOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
projectDisplayName: await getDisplayNameForProjectIdAsync(graphqlClient, projectId),
|
||||
title: `Select ${flags.selectedPlatform === AppPlatform.Ios ? 'iOS' : 'Android'} ${
|
||||
flags.selectedPlatform === AppPlatform.Ios ? 'simulator' : 'emulator'
|
||||
} build to run for ${await getDisplayNameForProjectIdAsync(graphqlClient, projectId)} app`,
|
||||
filter: {
|
||||
platform: flags.selectedPlatform,
|
||||
distribution: buildDistributionTypeToGraphQLDistributionType(
|
||||
BuildDistributionType.SIMULATOR
|
||||
),
|
||||
distribution: distributionType,
|
||||
status: BuildStatus.Finished,
|
||||
},
|
||||
queryOptions: paginatedQueryOptions,
|
||||
paginatedQueryOptions,
|
||||
selectPromptDisabledFunction: build => !isRunnableOnSimulatorOrEmulator(build),
|
||||
selectPromptWarningMessage:
|
||||
'Artifacts for this build have expired and are no longer available, or this is not a simulator/emulator build.',
|
||||
});
|
||||
|
||||
return build ?? null;
|
||||
} else if (flags.runArchiveFlags.latest) {
|
||||
return await getLatestBuildAsync(graphqlClient, {
|
||||
projectId,
|
||||
filter: {
|
||||
platform: flags.selectedPlatform,
|
||||
distribution: buildDistributionTypeToGraphQLDistributionType(
|
||||
BuildDistributionType.SIMULATOR
|
||||
),
|
||||
distribution: distributionType,
|
||||
status: BuildStatus.Finished,
|
||||
},
|
||||
});
|
||||
@@ -179,6 +198,17 @@ async function maybeGetBuildAsync(
|
||||
}
|
||||
}
|
||||
|
||||
function getEasBuildRunCachedAppPath(
|
||||
projectId: string,
|
||||
buildId: string,
|
||||
platform: AppPlatform
|
||||
): string {
|
||||
return path.join(
|
||||
getEasBuildRunCacheDirectoryPath(),
|
||||
`${projectId}_${buildId}.${platform === AppPlatform.Ios ? 'app' : 'apk'}`
|
||||
);
|
||||
}
|
||||
|
||||
async function getPathToSimulatorBuildAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
@@ -186,25 +216,40 @@ async function getPathToSimulatorBuildAppAsync(
|
||||
queryOptions: PaginatedQueryOptions
|
||||
): Promise<string> {
|
||||
const maybeBuild = await maybeGetBuildAsync(graphqlClient, flags, projectId, queryOptions);
|
||||
const appExtension = flags.selectedPlatform === AppPlatform.Ios ? 'app' : 'apk';
|
||||
|
||||
if (maybeBuild) {
|
||||
const cachedAppPath = getEasBuildRunCachedAppPath(
|
||||
projectId,
|
||||
maybeBuild.id,
|
||||
flags.selectedPlatform
|
||||
);
|
||||
|
||||
if (await pathExists(cachedAppPath)) {
|
||||
Log.newLine();
|
||||
Log.log(`Using cached app...`);
|
||||
return cachedAppPath;
|
||||
}
|
||||
|
||||
if (!maybeBuild.artifacts?.applicationArchiveUrl) {
|
||||
throw new Error('Build does not have an application archive url');
|
||||
}
|
||||
|
||||
return await downloadAndExtractAppAsync(
|
||||
return await downloadAndMaybeExtractAppAsync(
|
||||
maybeBuild.artifacts.applicationArchiveUrl,
|
||||
appExtension
|
||||
flags.selectedPlatform,
|
||||
cachedAppPath
|
||||
);
|
||||
}
|
||||
|
||||
if (flags.runArchiveFlags.url) {
|
||||
return await downloadAndExtractAppAsync(flags.runArchiveFlags.url, appExtension);
|
||||
return await downloadAndMaybeExtractAppAsync(flags.runArchiveFlags.url, flags.selectedPlatform);
|
||||
}
|
||||
|
||||
if (flags.runArchiveFlags.path?.endsWith('.tar.gz')) {
|
||||
return await extractAppFromLocalArchiveAsync(flags.runArchiveFlags.path!, appExtension);
|
||||
return await extractAppFromLocalArchiveAsync(
|
||||
flags.runArchiveFlags.path,
|
||||
flags.selectedPlatform
|
||||
);
|
||||
}
|
||||
|
||||
// this should never fail, due to the validation in sanitizeFlagsAsync
|
||||
|
||||
@@ -107,7 +107,7 @@ export default class ChannelEdit extends EasCommand {
|
||||
: await selectBranchOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
promptTitle: `Which branch would you like ${existingChannel.name} to point at?`,
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
displayTextForListItem: updateBranch => ({ title: updateBranch.name }),
|
||||
paginatedQueryOptions: {
|
||||
json,
|
||||
nonInteractive,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ExpoConfig } from '@expo/config-types';
|
||||
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';
|
||||
@@ -12,7 +11,7 @@ import { saveProjectIdToAppConfigAsync } from '../../commandUtils/context/contex
|
||||
import { AppPrivacy, Role } from '../../graphql/generated';
|
||||
import { AppMutation } from '../../graphql/mutations/AppMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import Log from '../../log';
|
||||
import Log, { link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getExpoConfig } from '../../project/expoConfig';
|
||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
@@ -303,10 +302,7 @@ export default class ProjectInit extends EasCommand {
|
||||
}
|
||||
|
||||
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
|
||||
const projectLink = terminalLink(projectFullName, projectDashboardUrl, {
|
||||
// https://github.com/sindresorhus/terminal-link/issues/18#issuecomment-1068020361
|
||||
fallback: () => `${projectFullName} (${projectDashboardUrl})`,
|
||||
});
|
||||
const projectLink = link(projectDashboardUrl, { text: projectFullName });
|
||||
|
||||
const account = nullthrows(allAccounts.find(a => a.name === accountName));
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import { AppJSONConfig, PackageJSONConfig, Platform, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { vol } from 'memfs';
|
||||
import path from 'path';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import UpdatePublish from '..';
|
||||
import { ensureBranchExistsAsync } from '../../../branch/queries';
|
||||
import { DynamicProjectConfigContextField } from '../../../commandUtils/context/DynamicProjectConfigContextField';
|
||||
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../../../commandUtils/gating/FeatureGating';
|
||||
import { jester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { PublishMutation } from '../../../graphql/mutations/PublishMutation';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { collectAssetsAsync, uploadAssetsAsync } from '../../../project/publish';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../../update/getBranchNameFromChannelNameAsync';
|
||||
|
||||
const projectRoot = '/test-project';
|
||||
const commandOptions = { root: projectRoot } as any;
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('@expo/config-plugins');
|
||||
jest.mock('../../../branch/queries');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
|
||||
jest.mock('../../../update/configure');
|
||||
jest.mock('../../../update/getBranchNameFromChannelNameAsync');
|
||||
jest.mock('../../../graphql/mutations/PublishMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/UpdateQuery');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: () => ({ succeed: () => {}, fail: () => {} }),
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../project/publish', () => ({
|
||||
...jest.requireActual('../../../project/publish'),
|
||||
buildBundlesAsync: jest.fn(),
|
||||
collectAssetsAsync: jest.fn(),
|
||||
resolveInputDirectoryAsync: jest.fn((inputDir = 'dist') => path.join(projectRoot, inputDir)),
|
||||
uploadAssetsAsync: jest.fn(),
|
||||
}));
|
||||
|
||||
describe(UpdatePublish.name, () => {
|
||||
afterEach(() => vol.reset());
|
||||
|
||||
// Deprecated and split to a new command: update:republish
|
||||
it('errors with --republish', async () => {
|
||||
await expect(new UpdatePublish(['--republish'], commandOptions).run()).rejects.toThrow(
|
||||
'--group and --republish flags are deprecated'
|
||||
);
|
||||
});
|
||||
|
||||
// Deprecated and split to a new command: update:republish
|
||||
it('errors with --group', async () => {
|
||||
await expect(new UpdatePublish(['--group=abc123'], commandOptions).run()).rejects.toThrow(
|
||||
'--group and --republish flags are deprecated'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors with both --channel and --branch', async () => {
|
||||
const flags = ['--channel=channel123', '--branch=branch123'];
|
||||
|
||||
mockTestProject();
|
||||
|
||||
await expect(new UpdatePublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
'Cannot specify both --channel and --branch. Specify either --channel, --branch, or --auto'
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a new update with --non-interactive, --branch, and --message', async () => {
|
||||
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||
|
||||
mockTestProject();
|
||||
const { platforms, runtimeVersion } = mockTestExport();
|
||||
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue(
|
||||
platforms.map(platform => ({
|
||||
id: 'update123',
|
||||
group: 'group123',
|
||||
runtimeVersion,
|
||||
platform,
|
||||
manifestPermalink: 'https://example.com/update123/manifest',
|
||||
}))
|
||||
);
|
||||
|
||||
await new UpdatePublish(flags, commandOptions).run();
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a new update with --non-interactive, --channel, and --message', async () => {
|
||||
const flags = ['--non-interactive', '--channel=channel123', '--message=abc'];
|
||||
|
||||
const { projectId } = mockTestProject();
|
||||
const { platforms, runtimeVersion } = mockTestExport();
|
||||
|
||||
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue(
|
||||
platforms.map(platform => ({
|
||||
id: 'update123',
|
||||
group: 'group123',
|
||||
runtimeVersion,
|
||||
platform,
|
||||
manifestPermalink: 'https://example.com/update123/manifest',
|
||||
}))
|
||||
);
|
||||
|
||||
await new UpdatePublish(flags, commandOptions).run();
|
||||
|
||||
expect(ensureBranchExistsAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
{
|
||||
appId: projectId,
|
||||
branchName: 'branchFromChannel',
|
||||
}
|
||||
);
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
/** Create a new in-memory project, copied from src/commands/update/__tests__/republish.test.ts */
|
||||
function mockTestProject({
|
||||
configuredProjectId = '1234',
|
||||
}: {
|
||||
configuredProjectId?: string;
|
||||
} = {}): { projectId: string } {
|
||||
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: jester.accounts[0].name,
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: configuredProjectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vol.fromJSON(
|
||||
{
|
||||
'package.json': JSON.stringify(packageJSON),
|
||||
'app.json': JSON.stringify(appJSON),
|
||||
},
|
||||
projectRoot
|
||||
);
|
||||
|
||||
const mockManifest = { exp: appJSON.expo };
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>({}));
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue(mockManifest as any);
|
||||
jest
|
||||
.spyOn(DynamicProjectConfigContextField.prototype, 'getValueAsync')
|
||||
.mockResolvedValue(async () => ({
|
||||
exp: mockManifest.exp,
|
||||
projectDir: projectRoot,
|
||||
projectId: configuredProjectId,
|
||||
}));
|
||||
|
||||
jest.spyOn(LoggedInContextField.prototype, 'getValueAsync').mockResolvedValue({
|
||||
actor: jester,
|
||||
featureGating: new FeatureGating({}, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
});
|
||||
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
return { projectId: configuredProjectId };
|
||||
}
|
||||
|
||||
/** Create a new in-memory export of the project */
|
||||
function mockTestExport({
|
||||
exportDir = 'dist',
|
||||
platforms = ['android', 'ios'],
|
||||
runtimeVersion = 'exposdk:47.0.0',
|
||||
}: {
|
||||
exportDir?: string;
|
||||
platforms?: Platform[];
|
||||
runtimeVersion?: string;
|
||||
} = {}): {
|
||||
inputDir: string;
|
||||
platforms: Platform[];
|
||||
runtimeVersion: string;
|
||||
} {
|
||||
/* eslint-disable node/no-sync */
|
||||
vol.mkdirpSync(path.join(projectRoot, exportDir, 'bundles'));
|
||||
for (const platform of platforms) {
|
||||
vol.writeFileSync(
|
||||
path.join(projectRoot, exportDir, 'bundles', `${platform}-fake.js`),
|
||||
`console.log("fake bundle for ${platform}");`
|
||||
);
|
||||
}
|
||||
|
||||
jest.mocked(collectAssetsAsync).mockResolvedValue(
|
||||
Object.fromEntries(
|
||||
platforms.map(platform => [
|
||||
platform,
|
||||
{
|
||||
assets: [],
|
||||
launchAsset: {
|
||||
contentType: 'application/javascript',
|
||||
path: path.join(projectRoot, exportDir, 'bundles', `${platform}-fake.js`),
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
jest.mocked(uploadAssetsAsync).mockResolvedValue({
|
||||
// platforms are mocked all containing only the launch asset
|
||||
assetCount: platforms.length,
|
||||
uniqueAssetCount: platforms.length,
|
||||
uniqueUploadedAssetCount: platforms.length,
|
||||
assetLimitPerUpdateGroup: 9001,
|
||||
});
|
||||
|
||||
jest.mocked(Updates.getRuntimeVersion).mockReturnValue(runtimeVersion);
|
||||
|
||||
return { inputDir: exportDir, platforms, runtimeVersion };
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
|
||||
import { vol } from 'memfs';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import LoggedInContextField from '../../../commandUtils/context/LoggedInContextField';
|
||||
import ProjectConfigContextField from '../../../commandUtils/context/ProjectConfigContextField';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import FeatureGateEnvOverrides from '../../../commandUtils/gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../../../commandUtils/gating/FeatureGating';
|
||||
import { jester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { CodeSigningInfo, UpdateFragment } from '../../../graphql/generated';
|
||||
import { PublishMutation } from '../../../graphql/mutations/PublishMutation';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { UpdateQuery } from '../../../graphql/queries/UpdateQuery';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../../update/getBranchNameFromChannelNameAsync';
|
||||
import { selectUpdateGroupOnBranchAsync } from '../../../update/queries';
|
||||
import UpdateRepublish from '../republish';
|
||||
|
||||
const projectRoot = '/test-project';
|
||||
const commandOptions = { root: projectRoot } as any;
|
||||
const updateStub: UpdateFragment = {
|
||||
id: 'update-1234',
|
||||
group: 'group-1234',
|
||||
branch: { id: 'branch-1234', name: 'main' },
|
||||
message: 'test message',
|
||||
runtimeVersion: 'exposdk:47.0.0',
|
||||
platform: 'ios',
|
||||
gitCommitHash: 'commit',
|
||||
manifestFragment: JSON.stringify({ fake: 'manifest' }),
|
||||
codeSigningInfo: null,
|
||||
createdAt: '2022-01-01T12:00:00Z',
|
||||
};
|
||||
|
||||
const codeSigningStub: CodeSigningInfo = {
|
||||
keyid: 'keyid',
|
||||
alg: 'alg',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync');
|
||||
jest.mock('../../../graphql/mutations/PublishMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/UpdateQuery');
|
||||
jest.mock('../../../update/getBranchNameFromChannelNameAsync');
|
||||
jest.mock('../../../update/queries');
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: () => ({ succeed: () => {}, fail: () => {} }),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe(UpdateRepublish.name, () => {
|
||||
afterEach(() => vol.reset());
|
||||
|
||||
it('errors without --channel, --branch, or --group', async () => {
|
||||
await expect(new UpdateRepublish([], commandOptions).run()).rejects.toThrow(
|
||||
'--channel, --branch, or --group must be specified'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when providing both --group and --branch', async () => {
|
||||
const flags = ['--group=1234', '--branch=main'];
|
||||
|
||||
mockTestProject();
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
/--branch=main cannot also be provided when using --group/
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when providing both --channel and --branch', async () => {
|
||||
const flags = ['--channel=main', '--branch=main'];
|
||||
|
||||
mockTestProject();
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
/--branch=main cannot also be provided when using --channel/
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when providing both --group and --channel', async () => {
|
||||
const flags = ['--group=1234', '--channel=main'];
|
||||
|
||||
mockTestProject();
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
/--channel=main cannot also be provided when using --group/
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when update group is empty', async () => {
|
||||
const flags = ['--group=1234'];
|
||||
|
||||
mockTestProject();
|
||||
jest.mocked(UpdateQuery.viewUpdateGroupAsync).mockResolvedValue([]);
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
'There are no published updates found'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when update group has no update for requested platform', async () => {
|
||||
const flags = ['--group=1234', '--platform=android'];
|
||||
|
||||
mockTestProject();
|
||||
jest.mocked(UpdateQuery.viewUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
platform: 'ios',
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(new UpdateRepublish(flags, commandOptions).run()).rejects.toThrow(
|
||||
'There are no updates on branch "main" published for the platform(s) "android" with group ID "1234". Did you mean to publish a new update instead?'
|
||||
);
|
||||
});
|
||||
|
||||
it('re-creates update with --group and --message', async () => {
|
||||
const flags = ['--group=1234', '--message=test-republish'];
|
||||
|
||||
mockTestProject();
|
||||
// Mock queries to retrieve the update and code signing info
|
||||
jest.mocked(UpdateQuery.viewUpdateGroupAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: updateStub.branch.id,
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
expect(PublishMutation.setCodeSigningInfoAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('re-creates update using codesigning with --group and --message', async () => {
|
||||
const flags = ['--group=1234', '--message=test-republish'];
|
||||
const codeSigning = {
|
||||
alg: 'alg',
|
||||
keyid: 'keyid',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
mockTestProject();
|
||||
// Mock queries to retrieve the update and code signing info
|
||||
jest
|
||||
.mocked(UpdateQuery.viewUpdateGroupAsync)
|
||||
.mockResolvedValue([{ ...updateStub, codeSigningInfo: codeSigningStub }]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
jest.mocked(PublishMutation.setCodeSigningInfoAsync).mockResolvedValue({} as any);
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: updateStub.branch.id,
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: true,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
expect(PublishMutation.setCodeSigningInfoAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
'update-new',
|
||||
expect.objectContaining(codeSigning)
|
||||
);
|
||||
});
|
||||
|
||||
it('re-creates update with --branch and --message', async () => {
|
||||
const flags = ['--branch=branch123', '--message=test-republish'];
|
||||
|
||||
mockTestProject();
|
||||
// Mock the prompt to ask the user which update to republish, from branch
|
||||
jest.mocked(selectUpdateGroupOnBranchAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(selectUpdateGroupOnBranchAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.objectContaining({ branchName: 'branch123' })
|
||||
);
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: updateStub.branch.id,
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('re-creates update with --channel and --message', async () => {
|
||||
const flags = ['--channel=channel123', '--message=test-republish'];
|
||||
|
||||
mockTestProject();
|
||||
// Mock resolving the channel to branch name, only valid for a single branch connected
|
||||
jest.mocked(getBranchNameFromChannelNameAsync).mockResolvedValue('branchFromChannel');
|
||||
// Mock the prompt to ask the user which update to republish, from branch
|
||||
jest.mocked(selectUpdateGroupOnBranchAsync).mockResolvedValue([updateStub]);
|
||||
// Mock mutations to store the new update
|
||||
jest.mocked(PublishMutation.publishUpdateGroupAsync).mockResolvedValue([
|
||||
{
|
||||
...updateStub,
|
||||
id: 'update-new',
|
||||
platform: 'ios',
|
||||
manifestPermalink: 'https://expo.dev/@test/test-project/manifest',
|
||||
},
|
||||
]);
|
||||
|
||||
await new UpdateRepublish(flags, commandOptions).run();
|
||||
|
||||
expect(selectUpdateGroupOnBranchAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.objectContaining({ branchName: 'branchFromChannel' })
|
||||
);
|
||||
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalledWith(
|
||||
expect.any(Object), // graphql client
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
branchId: updateStub.branch.id,
|
||||
runtimeVersion: updateStub.runtimeVersion,
|
||||
updateInfoGroup: expect.objectContaining({
|
||||
ios: expect.any(Object),
|
||||
}),
|
||||
gitCommitHash: updateStub.gitCommitHash,
|
||||
awaitingCodeSigningInfo: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/** Create a new in-memory project, based on src/commands/project/__tests__/init.test.ts */
|
||||
function mockTestProject({
|
||||
configuredProjectId = '1234',
|
||||
}: {
|
||||
configuredProjectId?: string;
|
||||
} = {}): void {
|
||||
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: jester.accounts[0].name,
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: configuredProjectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vol.fromJSON(
|
||||
{
|
||||
'package.json': JSON.stringify(packageJSON),
|
||||
'app.json': JSON.stringify(appJSON),
|
||||
},
|
||||
projectRoot
|
||||
);
|
||||
|
||||
const mockManifest = { exp: appJSON.expo };
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>({}));
|
||||
|
||||
jest.mocked(getConfig).mockReturnValue(mockManifest as any);
|
||||
jest.spyOn(ProjectConfigContextField.prototype, 'getValueAsync').mockResolvedValue({
|
||||
exp: mockManifest.exp,
|
||||
projectDir: projectRoot,
|
||||
projectId: configuredProjectId,
|
||||
});
|
||||
|
||||
jest.spyOn(LoggedInContextField.prototype, 'getValueAsync').mockResolvedValue({
|
||||
actor: jester,
|
||||
featureGating: new FeatureGating({}, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
});
|
||||
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import Log from '../../log';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { ensureEASUpdatesIsConfiguredAsync } from '../../update/configure';
|
||||
import {
|
||||
ensureEASUpdateIsConfiguredAsync,
|
||||
ensureEASUpdateIsConfiguredInEasJsonAsync,
|
||||
} from '../../update/configure';
|
||||
|
||||
export default class UpdateConfigure extends EasCommand {
|
||||
static override description = 'configure the project to support EAS Update';
|
||||
@@ -38,14 +42,33 @@ export default class UpdateConfigure extends EasCommand {
|
||||
'💡 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.'
|
||||
);
|
||||
|
||||
await ensureEASUpdatesIsConfiguredAsync(graphqlClient, {
|
||||
await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
|
||||
exp,
|
||||
projectId,
|
||||
projectDir,
|
||||
platform,
|
||||
});
|
||||
|
||||
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(`🎉 Your app is configured to run EAS Update!`);
|
||||
Log.log(`🎉 Your app is configured with EAS Update!`);
|
||||
Log.newLine();
|
||||
Log.log(`${chalk.bold('Next steps')}:`);
|
||||
Log.newLine();
|
||||
Log.log('Update a production build:');
|
||||
Log.log(`1. Create a new build. Example: ${chalk.bold('eas build --profile production')}.`);
|
||||
Log.log('2. Make changes in your project.');
|
||||
Log.log(`3. Publish an update. Example: ${chalk.bold('eas update --channel production')}.`);
|
||||
Log.log('4. Force close and reopen the app at least twice to view the update.');
|
||||
|
||||
Log.newLine();
|
||||
Log.log('Preview an update:');
|
||||
Log.log(
|
||||
`1. Publish an update to a branch. Example: ${chalk.bold('eas update --branch new-feature')}.`
|
||||
);
|
||||
Log.log(
|
||||
'2. In Expo Go or a development build, navigate to Projects > [project name] > Branch > Open.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,10 @@ import fetch from '../../fetch';
|
||||
import {
|
||||
PublishUpdateGroupInput,
|
||||
StatuspageServiceName,
|
||||
Update,
|
||||
UpdateInfoGroup,
|
||||
UpdatePublishMutation,
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform, requestedPlatformDisplayNames } from '../../platform';
|
||||
@@ -39,8 +37,8 @@ import {
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { ensureEASUpdatesIsConfiguredAsync } from '../../update/configure';
|
||||
import { selectUpdateGroupOnBranchAsync } from '../../update/queries';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../update/getBranchNameFromChannelNameAsync';
|
||||
import { formatUpdateMessage, truncateString as truncateUpdateMessage } from '../../update/utils';
|
||||
import {
|
||||
checkManifestBodyAgainstUpdateInfoGroup,
|
||||
@@ -59,24 +57,26 @@ export const defaultPublishPlatforms: Partial<PublishPlatform>[] = ['android', '
|
||||
type RawUpdateFlags = {
|
||||
auto: boolean;
|
||||
branch?: string;
|
||||
channel?: string;
|
||||
message?: string;
|
||||
group?: string;
|
||||
republish?: boolean;
|
||||
platform: string;
|
||||
'input-dir': string;
|
||||
'skip-bundler': boolean;
|
||||
'private-key-path'?: string;
|
||||
'non-interactive': boolean;
|
||||
json: boolean;
|
||||
/** @deprecated see UpdateRepublish command */
|
||||
group?: string;
|
||||
/** @deprecated see UpdateRepublish command */
|
||||
republish?: boolean;
|
||||
};
|
||||
|
||||
type UpdateFlags = {
|
||||
auto: boolean;
|
||||
platform: ExpoCLIExportPlatformFlag;
|
||||
branchName?: string;
|
||||
channelName?: string;
|
||||
updateMessage?: string;
|
||||
republish: boolean;
|
||||
groupId?: string;
|
||||
inputDir: string;
|
||||
skipBundler: boolean;
|
||||
privateKeyPath?: string;
|
||||
@@ -107,16 +107,20 @@ export default class UpdatePublish extends EasCommand {
|
||||
description: 'Branch to publish the update group on',
|
||||
required: false,
|
||||
}),
|
||||
channel: Flags.string({
|
||||
description: 'Channel that the published update should affect',
|
||||
required: false,
|
||||
}),
|
||||
message: Flags.string({
|
||||
description: 'A short message describing the update',
|
||||
required: false,
|
||||
}),
|
||||
republish: Flags.boolean({
|
||||
description: 'Republish an update group',
|
||||
description: 'Republish an update group (deprecated, see republish command)',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
group: Flags.string({
|
||||
description: 'Update group to republish',
|
||||
description: 'Update group to republish (deprecated, see republish command)',
|
||||
exclusive: ['input-dir', 'skip-bundler'],
|
||||
}),
|
||||
'input-dir': Flags.string({
|
||||
@@ -161,16 +165,15 @@ export default class UpdatePublish extends EasCommand {
|
||||
let {
|
||||
auto: autoFlag,
|
||||
platform: platformFlag,
|
||||
branchName,
|
||||
channelName,
|
||||
updateMessage,
|
||||
republish,
|
||||
groupId,
|
||||
inputDir,
|
||||
skipBundler,
|
||||
privateKeyPath,
|
||||
json: jsonFlag,
|
||||
nonInteractive,
|
||||
} = this.sanitizeFlags(rawFlags);
|
||||
let branchName = this.sanitizeFlags(rawFlags).branchName;
|
||||
|
||||
const {
|
||||
getDynamicProjectConfigAsync,
|
||||
@@ -197,7 +200,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
|
||||
await maybeWarnAboutEasOutagesAsync(graphqlClient, [StatuspageServiceName.EasUpdate]);
|
||||
|
||||
await ensureEASUpdatesIsConfiguredAsync(graphqlClient, {
|
||||
await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
|
||||
exp: expPossiblyWithoutEasUpdateConfigured,
|
||||
platform: getRequestedPlatform(platformFlag),
|
||||
projectDir,
|
||||
@@ -209,22 +212,31 @@ export default class UpdatePublish extends EasCommand {
|
||||
|
||||
let realizedPlatforms: PublishPlatform[] = [];
|
||||
|
||||
if (channelName && branchName) {
|
||||
throw new Error(
|
||||
'Cannot specify both --channel and --branch. Specify either --channel, --branch, or --auto'
|
||||
);
|
||||
}
|
||||
|
||||
if (channelName) {
|
||||
branchName = await getBranchNameFromChannelNameAsync(graphqlClient, projectId, channelName);
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
if (autoFlag) {
|
||||
branchName = await getDefaultBranchNameAsync();
|
||||
} else if (nonInteractive) {
|
||||
throw new Error('Must supply --branch or use --auto when in non-interactive mode');
|
||||
throw new Error('Must supply --channel, --branch or --auto when in non-interactive mode');
|
||||
} else {
|
||||
try {
|
||||
const branch = await selectBranchOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
promptTitle: `Which branch would you like to ${
|
||||
republish ? 'republish' : 'publish'
|
||||
} on?`,
|
||||
displayTextForListItem: updateBranch =>
|
||||
`${updateBranch.name} ${chalk.grey(
|
||||
promptTitle: `Which branch would you like to publish on?`,
|
||||
displayTextForListItem: updateBranch => ({
|
||||
title: `${updateBranch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdateMessage(updateBranch.updates[0])}`
|
||||
)}`,
|
||||
}),
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
branchName = branch.name;
|
||||
@@ -243,156 +255,72 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateMessage && autoFlag) {
|
||||
updateMessage = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!updateMessage) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --message or use --auto when in non-interactive mode');
|
||||
}
|
||||
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ updateMessage } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'updateMessage',
|
||||
message: `Provide an update message.`,
|
||||
initial: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
const bundleSpinner = ora().start('Exporting...');
|
||||
try {
|
||||
await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag });
|
||||
bundleSpinner.succeed('Exported bundle(s)');
|
||||
} catch (e) {
|
||||
bundleSpinner.fail('Export failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// After possibly bundling, assert that the input directory can be found.
|
||||
const distRoot = await resolveInputDirectoryAsync(inputDir, { skipBundler });
|
||||
|
||||
const assetSpinner = ora().start('Uploading...');
|
||||
let unsortedUpdateInfoGroups: UpdateInfoGroup = {};
|
||||
let oldMessage: string, oldRuntimeVersion: string;
|
||||
let uploadedAssetCount = 0;
|
||||
let assetLimitPerUpdateGroup = 0;
|
||||
|
||||
if (republish) {
|
||||
// If we are republishing, we don't need to worry about building the bundle or uploading the assets.
|
||||
// Instead we get the `updateInfoGroup` from the update we wish to republish.
|
||||
let updatesToRepublish: Pick<
|
||||
Update,
|
||||
'group' | 'message' | 'runtimeVersion' | 'manifestFragment' | 'platform'
|
||||
>[];
|
||||
if (groupId) {
|
||||
const updatesByGroup = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
|
||||
groupId,
|
||||
});
|
||||
updatesToRepublish = updatesByGroup;
|
||||
} else {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --group when in non-interactive mode');
|
||||
try {
|
||||
const collectedAssets = await collectAssetsAsync(distRoot);
|
||||
const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag);
|
||||
realizedPlatforms = Object.keys(assets) as PublishPlatform[];
|
||||
|
||||
const uploadResults = await uploadAssetsAsync(
|
||||
graphqlClient,
|
||||
assets,
|
||||
projectId,
|
||||
(totalAssets, missingAssets) => {
|
||||
assetSpinner.text = `Uploading (${totalAssets - missingAssets}/${totalAssets})`;
|
||||
}
|
||||
|
||||
updatesToRepublish = await selectUpdateGroupOnBranchAsync(graphqlClient, {
|
||||
projectId,
|
||||
branchName,
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
}
|
||||
const updatesToRepublishFilteredByPlatform = updatesToRepublish.filter(
|
||||
// Only republish to the specified platforms
|
||||
update => platformFlag === 'all' || update.platform === platformFlag
|
||||
);
|
||||
if (updatesToRepublishFilteredByPlatform.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${branchName}" published for the platform(s) "${platformFlag}" with group ID "${
|
||||
groupId ? groupId : updatesToRepublish[0].group
|
||||
}". Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
let publicationPlatformMessage: string;
|
||||
if (platformFlag === 'all') {
|
||||
if (updatesToRepublishFilteredByPlatform.length < defaultPublishPlatforms.length) {
|
||||
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
|
||||
}
|
||||
publicationPlatformMessage = `The republished update will appear on the same platforms it was originally published on: ${updatesToRepublishFilteredByPlatform
|
||||
.map(update => update.platform)
|
||||
.join(', ')}`;
|
||||
} else {
|
||||
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
|
||||
}
|
||||
Log.withTick(publicationPlatformMessage);
|
||||
|
||||
for (const update of updatesToRepublishFilteredByPlatform) {
|
||||
const { manifestFragment } = update;
|
||||
const platform = update.platform as PublishPlatform;
|
||||
|
||||
unsortedUpdateInfoGroups[platform] = JSON.parse(manifestFragment);
|
||||
}
|
||||
realizedPlatforms = updatesToRepublishFilteredByPlatform.map(
|
||||
update => update.platform as PublishPlatform
|
||||
);
|
||||
|
||||
// These are the same for each member of an update group
|
||||
groupId = updatesToRepublishFilteredByPlatform[0].group;
|
||||
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
|
||||
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
|
||||
|
||||
if (!updateMessage) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --message when in non-interactive mode');
|
||||
}
|
||||
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ updateMessage } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'updateMessage',
|
||||
message: `Provide an update message.`,
|
||||
initial: `Republish "${oldMessage!}" - group: ${groupId}`,
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (!updateMessage && autoFlag) {
|
||||
updateMessage = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
}
|
||||
|
||||
if (!updateMessage) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --message or use --auto when in non-interactive mode');
|
||||
}
|
||||
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ updateMessage } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'updateMessage',
|
||||
message: `Provide an update message.`,
|
||||
initial: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
// build bundle and upload assets for a new publish
|
||||
if (!skipBundler) {
|
||||
const bundleSpinner = ora().start('Exporting...');
|
||||
try {
|
||||
await buildBundlesAsync({ projectDir, inputDir, exp, platformFlag });
|
||||
bundleSpinner.succeed('Exported bundle(s)');
|
||||
} catch (e) {
|
||||
bundleSpinner.fail('Export failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// After possibly bundling, assert that the input directory can be found.
|
||||
const distRoot = await resolveInputDirectoryAsync(inputDir, { skipBundler });
|
||||
|
||||
const assetSpinner = ora().start('Uploading...');
|
||||
|
||||
try {
|
||||
const collectedAssets = await collectAssetsAsync(distRoot);
|
||||
const assets = filterExportedPlatformsByFlag(collectedAssets, platformFlag);
|
||||
realizedPlatforms = Object.keys(assets) as PublishPlatform[];
|
||||
|
||||
const uploadResults = await uploadAssetsAsync(
|
||||
graphqlClient,
|
||||
assets,
|
||||
projectId,
|
||||
(totalAssets, missingAssets) => {
|
||||
assetSpinner.text = `Uploading (${totalAssets - missingAssets}/${totalAssets})`;
|
||||
}
|
||||
);
|
||||
|
||||
uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
|
||||
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
const uploadAssetSuccessMessage = uploadedAssetCount
|
||||
? `Uploaded ${uploadedAssetCount} ${uploadedAssetCount === 1 ? 'platform' : 'platforms'}`
|
||||
: `Uploaded: No changes detected`;
|
||||
assetSpinner.succeed(uploadAssetSuccessMessage);
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload');
|
||||
throw e;
|
||||
}
|
||||
uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
|
||||
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
|
||||
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);
|
||||
const uploadAssetSuccessMessage = uploadedAssetCount
|
||||
? `Uploaded ${uploadedAssetCount} ${uploadedAssetCount === 1 ? 'platform' : 'platforms'}`
|
||||
: `Uploaded: No changes detected`;
|
||||
assetSpinner.succeed(uploadAssetSuccessMessage);
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload');
|
||||
throw e;
|
||||
}
|
||||
|
||||
const truncatedMessage = truncateUpdateMessage(updateMessage!, 1024);
|
||||
@@ -412,18 +340,24 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branchId, createdBranch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
});
|
||||
await ensureChannelExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchId,
|
||||
channelName: branchName,
|
||||
});
|
||||
if (createdBranch) {
|
||||
await ensureChannelExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchId,
|
||||
channelName: branchName,
|
||||
});
|
||||
}
|
||||
|
||||
Log.withTick(`Channel: ${chalk.bold(branchName)} pointed at branch: ${chalk.bold(branchName)}`);
|
||||
|
||||
const gitCommitHash = await getVcsClient().getCommitHashAsync();
|
||||
const vcsClient = getVcsClient();
|
||||
|
||||
const gitCommitHash = await vcsClient.getCommitHashAsync();
|
||||
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
|
||||
|
||||
// Sort the updates into different groups based on their platform specific runtime versions
|
||||
const updateGroups: PublishUpdateGroupInput[] = runtimeToPlatformMapping.map(
|
||||
@@ -435,17 +369,13 @@ export default class UpdatePublish extends EasCommand {
|
||||
])
|
||||
);
|
||||
|
||||
if (republish && !oldRuntimeVersion) {
|
||||
throw new Error(
|
||||
'Cannot find the runtime version of the update group that is being republished.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
branchId,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
runtimeVersion: republish ? oldRuntimeVersion : runtimeVersion,
|
||||
runtimeVersion,
|
||||
message: truncatedMessage,
|
||||
gitCommitHash,
|
||||
isGitWorkingTreeDirty,
|
||||
awaitingCodeSigningInfo: !!codeSigningInfo,
|
||||
};
|
||||
}
|
||||
@@ -547,7 +477,14 @@ export default class UpdatePublish extends EasCommand {
|
||||
: []),
|
||||
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
|
||||
{ label: 'Message', value: truncatedMessage },
|
||||
...(gitCommitHash ? [{ label: 'Commit', value: gitCommitHash }] : []),
|
||||
...(gitCommitHash
|
||||
? [
|
||||
{
|
||||
label: 'Commit',
|
||||
value: `${gitCommitHash}${isGitWorkingTreeDirty ? '*' : ''}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: 'Website link', value: updateGroupLink },
|
||||
])
|
||||
);
|
||||
@@ -571,26 +508,36 @@ export default class UpdatePublish extends EasCommand {
|
||||
private sanitizeFlags(flags: RawUpdateFlags): UpdateFlags {
|
||||
const nonInteractive = flags['non-interactive'] ?? false;
|
||||
|
||||
const { auto, branch: branchName, message: updateMessage } = flags;
|
||||
if (nonInteractive && !auto && !(branchName && updateMessage)) {
|
||||
const { auto, branch: branchName, channel: channelName, message: updateMessage } = flags;
|
||||
if (nonInteractive && !auto && !(updateMessage && (branchName || channelName))) {
|
||||
Errors.error(
|
||||
'--auto or both --branch and --message are required when updating in non-interactive mode',
|
||||
'--branch and --message, or --channel and --message are required when updating in non-interactive mode unless --auto is specified',
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
|
||||
const groupId = flags.group;
|
||||
const republish = flags.republish || !!groupId; // When --group is defined, we are republishing
|
||||
if (nonInteractive && republish && !groupId) {
|
||||
Errors.error(`--group is required when updating in non-interactive mode`, { exit: 1 });
|
||||
if (flags.group || flags.republish) {
|
||||
// Pick the first flag set that is defined, in this specific order
|
||||
const args = [
|
||||
['--group', flags.group],
|
||||
['--branch', flags.branch],
|
||||
].filter(([_, value]) => value)[0];
|
||||
|
||||
Log.newLine();
|
||||
Log.warn(
|
||||
'The --group and --republish flags are deprecated, use the republish command instead:'
|
||||
);
|
||||
Log.warn(` ${chalk.bold([`eas update:republish`, ...(args ?? [])].join(' '))}`);
|
||||
Log.newLine();
|
||||
|
||||
Errors.error('--group and --republish flags are deprecated', { exit: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
auto,
|
||||
branchName,
|
||||
channelName,
|
||||
updateMessage,
|
||||
groupId,
|
||||
republish,
|
||||
inputDir: flags['input-dir'],
|
||||
skipBundler: flags['skip-bundler'],
|
||||
platform: flags.platform as RequestedPlatform,
|
||||
|
||||
@@ -70,7 +70,9 @@ export default class UpdateList extends EasCommand {
|
||||
const selectedBranch = await selectBranchOnAppAsync(graphqlClient, {
|
||||
projectId,
|
||||
promptTitle: 'Which branch would you like to view?',
|
||||
displayTextForListItem: updateBranch => updateBranch.name,
|
||||
displayTextForListItem: updateBranch => ({
|
||||
title: updateBranch.name,
|
||||
}),
|
||||
paginatedQueryOptions:
|
||||
// discard limit and offset because this query is not those flag's intended target
|
||||
{
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import { Platform } from '@expo/config';
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
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 { Update } from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import { UpdateQuery } from '../../graphql/queries/UpdateQuery';
|
||||
import Log, { link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { getBranchNameFromChannelNameAsync } from '../../update/getBranchNameFromChannelNameAsync';
|
||||
import { selectUpdateGroupOnBranchAsync } from '../../update/queries';
|
||||
import { truncateString as truncateUpdateMessage } from '../../update/utils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
const defaultRepublishPlatforms: Platform[] = ['android', 'ios'];
|
||||
|
||||
type UpdateRepublishRawFlags = {
|
||||
branch?: string;
|
||||
channel?: string;
|
||||
group?: string;
|
||||
message?: string;
|
||||
platform: string;
|
||||
'non-interactive': boolean;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
type UpdateRepublishFlags = {
|
||||
branchName?: string;
|
||||
channelName?: string;
|
||||
groupId?: string;
|
||||
updateMessage?: string;
|
||||
platform: Platform[];
|
||||
nonInteractive: boolean;
|
||||
json: boolean;
|
||||
};
|
||||
|
||||
type UpdateToRepublish = {
|
||||
groupId: string;
|
||||
branchId: string;
|
||||
branchName: string;
|
||||
} & Pick<
|
||||
Update,
|
||||
| 'message'
|
||||
| 'runtimeVersion'
|
||||
| 'manifestFragment'
|
||||
| 'platform'
|
||||
| 'gitCommitHash'
|
||||
| 'codeSigningInfo'
|
||||
>;
|
||||
|
||||
export default class UpdateRepublish extends EasCommand {
|
||||
static override description = 'rollback to an existing update';
|
||||
|
||||
static override flags = {
|
||||
channel: Flags.string({
|
||||
description: 'Channel name to select an update to republish from',
|
||||
exclusive: ['branch', 'group'],
|
||||
}),
|
||||
branch: Flags.string({
|
||||
description: 'Branch name to select an update to republish from',
|
||||
exclusive: ['channel', 'group'],
|
||||
}),
|
||||
group: Flags.string({
|
||||
description: 'Update group ID to republish',
|
||||
exclusive: ['branch', 'channel'],
|
||||
}),
|
||||
message: Flags.string({
|
||||
description: 'Short message describing the republished update',
|
||||
required: false,
|
||||
}),
|
||||
platform: Flags.enum({
|
||||
char: 'p',
|
||||
options: [...defaultRepublishPlatforms, 'all'],
|
||||
default: 'all',
|
||||
required: false,
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = await this.parse(UpdateRepublish);
|
||||
const flags = this.sanitizeFlags(rawFlags);
|
||||
|
||||
const {
|
||||
projectConfig: { exp, projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateRepublish, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
|
||||
if (flags.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const existingUpdates = await getOrAskUpdatesAsync(graphqlClient, projectId, flags);
|
||||
const updatesToPublish = existingUpdates.filter(update =>
|
||||
flags.platform.includes(update.platform as Platform)
|
||||
);
|
||||
|
||||
if (existingUpdates.length === 0) {
|
||||
throw new Error(`There are no published updates found`);
|
||||
}
|
||||
if (updatesToPublish.length === 0) {
|
||||
throw new Error(
|
||||
`There are no updates on branch "${
|
||||
existingUpdates[0].branchName
|
||||
}" published for the platform(s) "${rawFlags.platform}" with group ID "${
|
||||
flags.groupId ? flags.groupId : updatesToPublish[0].groupId
|
||||
}". Did you mean to publish a new update instead?`
|
||||
);
|
||||
}
|
||||
|
||||
if (rawFlags.platform === 'all') {
|
||||
Log.withTick(`The republished update will appear only on: ${rawFlags.platform}`);
|
||||
} else {
|
||||
const platformsFromUpdates = updatesToPublish.map(update => update.platform);
|
||||
if (platformsFromUpdates.length < defaultRepublishPlatforms.length) {
|
||||
Log.warn(`You are republishing an update that wasn't published for all platforms.`);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`The republished update will appear on the same platforms it was originally published on: ${platformsFromUpdates.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
// This command only republishes a single update group
|
||||
// The update group properties are the same for all updates
|
||||
const { branchId, branchName, runtimeVersion } = updatesToPublish[0];
|
||||
|
||||
const updateMessage = await getOrAskUpdateMessageAsync(updatesToPublish, flags);
|
||||
|
||||
// If codesigning was created for the original update, we need to add it to the republish
|
||||
const shouldRepublishWithCodesigning = updatesToPublish.some(update => update.codeSigningInfo);
|
||||
if (shouldRepublishWithCodesigning) {
|
||||
Log.withTick(
|
||||
`The republished update will be signed with the same codesigning as the original update.`
|
||||
);
|
||||
}
|
||||
|
||||
const publishIndicator = ora('Republishing...').start();
|
||||
let updatesRepublished: Awaited<ReturnType<typeof PublishMutation.publishUpdateGroupAsync>>;
|
||||
|
||||
try {
|
||||
updatesRepublished = await PublishMutation.publishUpdateGroupAsync(graphqlClient, [
|
||||
{
|
||||
branchId,
|
||||
runtimeVersion,
|
||||
message: updateMessage,
|
||||
updateInfoGroup: Object.fromEntries(
|
||||
updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)])
|
||||
),
|
||||
gitCommitHash: updatesToPublish[0].gitCommitHash,
|
||||
awaitingCodeSigningInfo: shouldRepublishWithCodesigning,
|
||||
},
|
||||
]);
|
||||
|
||||
if (shouldRepublishWithCodesigning) {
|
||||
const codeSigningByPlatform = Object.fromEntries(
|
||||
updatesToPublish.map(update => [update.platform, update.codeSigningInfo])
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
updatesRepublished.map(async update => {
|
||||
const codeSigning = codeSigningByPlatform[update.platform];
|
||||
if (codeSigning) {
|
||||
await PublishMutation.setCodeSigningInfoAsync(graphqlClient, update.id, codeSigning);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
publishIndicator.succeed('Republished update');
|
||||
} catch (error: any) {
|
||||
publishIndicator.fail('Failed to republish update');
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (flags.json) {
|
||||
return printJsonOnlyOutput(updatesRepublished);
|
||||
}
|
||||
|
||||
const updatesRepublishedByPlatform = Object.fromEntries(
|
||||
updatesRepublished.map(update => [update.platform, update])
|
||||
);
|
||||
|
||||
const updateGroupUrl = getUpdateGroupUrl(
|
||||
(await getOwnerAccountForProjectIdAsync(graphqlClient, projectId)).name,
|
||||
exp.slug,
|
||||
updatesRepublished[0].group
|
||||
);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Branch', value: branchName },
|
||||
{ label: 'Runtime version', value: updatesRepublished[0].runtimeVersion },
|
||||
{ label: 'Platform', value: updatesRepublished.map(update => update.platform).join(', ') },
|
||||
{ label: 'Update Group ID', value: updatesRepublished[0].id },
|
||||
...(updatesRepublishedByPlatform.android
|
||||
? [{ label: 'Android update ID', value: updatesRepublishedByPlatform.android.id }]
|
||||
: []),
|
||||
...(updatesRepublishedByPlatform.ios
|
||||
? [{ label: 'iOS update ID', value: updatesRepublishedByPlatform.ios.id }]
|
||||
: []),
|
||||
{ label: 'Message', value: updateMessage },
|
||||
{ label: 'Website link', value: link(updateGroupUrl, { dim: false }) },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
sanitizeFlags(rawFlags: UpdateRepublishRawFlags): UpdateRepublishFlags {
|
||||
const branchName = rawFlags.branch;
|
||||
const channelName = rawFlags.channel;
|
||||
const groupId = rawFlags.group;
|
||||
const nonInteractive = rawFlags['non-interactive'];
|
||||
|
||||
if (nonInteractive && !groupId) {
|
||||
throw new Error('Only --group can be used in non-interactive mode');
|
||||
}
|
||||
if (!groupId && !(branchName || channelName)) {
|
||||
throw new Error(`--channel, --branch, or --group must be specified`);
|
||||
}
|
||||
|
||||
const platform =
|
||||
rawFlags.platform === 'all' ? defaultRepublishPlatforms : ([rawFlags.platform] as Platform[]);
|
||||
|
||||
return {
|
||||
branchName,
|
||||
channelName,
|
||||
groupId,
|
||||
platform,
|
||||
updateMessage: rawFlags.message,
|
||||
json: rawFlags.json ?? false,
|
||||
nonInteractive,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Retrieve the update group from either the update group id, or select from branch name. */
|
||||
async function getOrAskUpdatesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
flags: UpdateRepublishFlags
|
||||
): Promise<UpdateToRepublish[]> {
|
||||
if (flags.groupId) {
|
||||
const updateGroups = await UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
|
||||
groupId: flags.groupId,
|
||||
});
|
||||
|
||||
return updateGroups.map(group => ({
|
||||
...group,
|
||||
groupId: group.group,
|
||||
branchId: group.branch.id,
|
||||
branchName: group.branch.name,
|
||||
}));
|
||||
}
|
||||
|
||||
if (flags.branchName) {
|
||||
return await askUpdatesFromBranchNameAsync(graphqlClient, {
|
||||
...flags,
|
||||
branchName: flags.branchName,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
|
||||
if (flags.channelName) {
|
||||
return await askUpdatesFromChannelNameAsync(graphqlClient, {
|
||||
...flags,
|
||||
channelName: flags.channelName,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error('--channel, --branch, or --group is required');
|
||||
}
|
||||
|
||||
/** Ask the user which update needs to be republished by branch name, this requires interactive mode */
|
||||
async function askUpdatesFromBranchNameAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
branchName,
|
||||
json,
|
||||
nonInteractive,
|
||||
}: { projectId: string; branchName: string; json: boolean; nonInteractive: boolean }
|
||||
): Promise<UpdateToRepublish[]> {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --group when in non-interactive mode');
|
||||
}
|
||||
|
||||
const updateGroups = await selectUpdateGroupOnBranchAsync(graphqlClient, {
|
||||
projectId,
|
||||
branchName,
|
||||
paginatedQueryOptions: getPaginatedQueryOptions({ json, 'non-interactive': nonInteractive }),
|
||||
});
|
||||
|
||||
return updateGroups.map(group => ({
|
||||
...group,
|
||||
groupId: group.id,
|
||||
branchId: group.branch.id,
|
||||
branchName: group.branch.name,
|
||||
}));
|
||||
}
|
||||
/** Ask the user which update needs to be republished by channel name, this requires interactive mode */
|
||||
async function askUpdatesFromChannelNameAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
projectId,
|
||||
channelName,
|
||||
json,
|
||||
nonInteractive,
|
||||
}: { projectId: string; channelName: string; json: boolean; nonInteractive: boolean }
|
||||
): Promise<UpdateToRepublish[]> {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Must supply --group when in non-interactive mode');
|
||||
}
|
||||
|
||||
const branchName = await getBranchNameFromChannelNameAsync(graphqlClient, projectId, channelName);
|
||||
|
||||
return await askUpdatesFromBranchNameAsync(graphqlClient, {
|
||||
projectId,
|
||||
branchName,
|
||||
json,
|
||||
nonInteractive,
|
||||
});
|
||||
}
|
||||
|
||||
/** Get or ask the user for the update (group) message for the republish */
|
||||
async function getOrAskUpdateMessageAsync(
|
||||
updates: UpdateToRepublish[],
|
||||
flags: UpdateRepublishFlags
|
||||
): Promise<string> {
|
||||
if (flags.updateMessage) {
|
||||
return sanitizeUpdateMessage(flags.updateMessage);
|
||||
}
|
||||
|
||||
if (flags.nonInteractive || flags.json) {
|
||||
throw new Error('Must supply --message when in non-interactive mode');
|
||||
}
|
||||
|
||||
// This command only uses a single update group to republish, meaning these values are always identical
|
||||
const oldGroupId = updates[0].groupId;
|
||||
const oldUpdateMessage = updates[0].message;
|
||||
|
||||
const { updateMessage } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'updateMessage',
|
||||
message: 'Provide an update message.',
|
||||
initial: `Republish "${oldUpdateMessage!}" - group: ${oldGroupId}`,
|
||||
validate: (value: any) => (value ? true : 'Update message may not be empty.'),
|
||||
});
|
||||
|
||||
return sanitizeUpdateMessage(updateMessage);
|
||||
}
|
||||
|
||||
function sanitizeUpdateMessage(updateMessage: string): string {
|
||||
if (updateMessage !== truncateUpdateMessage(updateMessage, 1024)) {
|
||||
Log.warn('Update message exceeds the allowed 1024 character limit, truncated update message.');
|
||||
return truncateUpdateMessage(updateMessage, 1024);
|
||||
}
|
||||
|
||||
return updateMessage;
|
||||
}
|
||||
@@ -2,11 +2,13 @@ import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasJsonOnlyFlag } from '../../commandUtils/flags';
|
||||
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 { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { formatWebhook } from '../../webhooks/formatWebhook';
|
||||
|
||||
export default class WebhookList extends EasCommand {
|
||||
@@ -17,6 +19,7 @@ export default class WebhookList extends EasCommand {
|
||||
description: 'Event type that triggers the webhook',
|
||||
options: [WebhookType.Build, WebhookType.Submit],
|
||||
}),
|
||||
...EasJsonOnlyFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
@@ -26,8 +29,12 @@ export default class WebhookList extends EasCommand {
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags: { event },
|
||||
flags: { event, json },
|
||||
} = await this.parse(WebhookList);
|
||||
if (json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const {
|
||||
projectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
@@ -48,10 +55,15 @@ export default class WebhookList extends EasCommand {
|
||||
spinner.fail(`There are no webhooks on project ${projectDisplayName}`);
|
||||
} else {
|
||||
spinner.succeed(`Found ${webhooks.length} webhooks on project ${projectDisplayName}`);
|
||||
const list = webhooks
|
||||
.map(webhook => formatWebhook(webhook))
|
||||
.join(`\n\n${chalk.dim('———')}\n\n`);
|
||||
Log.log(`\n${list}`);
|
||||
|
||||
if (json) {
|
||||
printJsonOnlyOutput(webhooks);
|
||||
} else {
|
||||
const list = webhooks
|
||||
.map(webhook => formatWebhook(webhook))
|
||||
.join(`\n\n${chalk.dim('———')}\n\n`);
|
||||
Log.log(`\n${list}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
spinner.fail(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import chalk from 'chalk';
|
||||
import terminalLink from 'terminal-link';
|
||||
// @ts-ignore
|
||||
import TurndownService from 'turndown';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
|
||||
import { link } from '../../../log';
|
||||
|
||||
const turndownServices: Record<string, any> = {};
|
||||
|
||||
function getService(rootUrl: string): TurndownService {
|
||||
@@ -44,10 +45,7 @@ function getService(rootUrl: string): TurndownService {
|
||||
if (href.startsWith('/')) {
|
||||
href = `${rootUrl}${href}`;
|
||||
}
|
||||
if (terminalLink.isSupported) {
|
||||
return chalk.cyan(terminalLink(content, href));
|
||||
}
|
||||
return `${chalk.cyan(content)} (${chalk.underline(href)})`;
|
||||
return chalk.cyan(link(href, { text: content }));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -44,10 +44,11 @@ export async function selectAppleTeamOnAccountAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: selectionPromptTitle,
|
||||
createDisplayTextForSelectionPromptListItem: appleTeam =>
|
||||
appleTeam.appleTeamName
|
||||
makePartialChoiceObject: appleTeam => ({
|
||||
title: appleTeam.appleTeamName
|
||||
? `${appleTeam.appleTeamName} (ID: ${appleTeam.appleTeamIdentifier})`
|
||||
: appleTeam.appleTeamIdentifier,
|
||||
}),
|
||||
getIdentifierForQueryItem: appleTeam => appleTeam.id,
|
||||
},
|
||||
});
|
||||
@@ -87,7 +88,9 @@ export async function selectAppleDeviceOnAppleTeamAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: selectionPromptTitle,
|
||||
createDisplayTextForSelectionPromptListItem: appleDevice => formatDeviceLabel(appleDevice),
|
||||
makePartialChoiceObject: appleDevice => ({
|
||||
title: formatDeviceLabel(appleDevice),
|
||||
}),
|
||||
getIdentifierForQueryItem: appleTeam => appleTeam.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,13 +23,12 @@ function formatDeviceClass(device: Device | NewDevice): string {
|
||||
|
||||
export default function formatDevice(device: Device, team?: AppleTeamIdAndName): string {
|
||||
const fields: FormatFieldsItem[] = [
|
||||
{ label: 'ID', value: device.id },
|
||||
{ label: 'UDID', value: device.identifier },
|
||||
{ label: 'Name', value: device.name ?? 'Unknown' },
|
||||
{
|
||||
label: 'Class',
|
||||
value: formatDeviceClass(device),
|
||||
},
|
||||
{ label: 'UDID', value: device.identifier },
|
||||
];
|
||||
|
||||
if (team) {
|
||||
|
||||
+112
-21
@@ -400,7 +400,6 @@ export type AccountSsoConfiguration = {
|
||||
id: Scalars['ID'];
|
||||
issuer?: Maybe<Scalars['String']>;
|
||||
jwksEndpoint?: Maybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
revokeEndpoint?: Maybe<Scalars['String']>;
|
||||
tokenEndpoint?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
@@ -415,7 +414,6 @@ export type AccountSsoConfigurationData = {
|
||||
clientSecret: Scalars['String'];
|
||||
issuer?: InputMaybe<Scalars['String']>;
|
||||
jwksEndpoint?: InputMaybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
revokeEndpoint?: InputMaybe<Scalars['String']>;
|
||||
tokenEndpoint?: InputMaybe<Scalars['String']>;
|
||||
userInfoEndpoint?: InputMaybe<Scalars['String']>;
|
||||
@@ -458,7 +456,6 @@ export type AccountSsoConfigurationPublicData = {
|
||||
id: Scalars['ID'];
|
||||
issuer?: Maybe<Scalars['String']>;
|
||||
jwksEndpoint?: Maybe<Scalars['String']>;
|
||||
redirectUri: Scalars['String'];
|
||||
revokeEndpoint?: Maybe<Scalars['String']>;
|
||||
tokenEndpoint?: Maybe<Scalars['String']>;
|
||||
userInfoEndpoint?: Maybe<Scalars['String']>;
|
||||
@@ -787,6 +784,7 @@ export type AndroidJobInput = {
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']>;
|
||||
gradleCommand?: InputMaybe<Scalars['String']>;
|
||||
mode?: InputMaybe<BuildMode>;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: InputMaybe<Scalars['String']>;
|
||||
@@ -815,6 +813,7 @@ export type AndroidJobOverridesInput = {
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']>;
|
||||
gradleCommand?: InputMaybe<Scalars['String']>;
|
||||
mode?: InputMaybe<BuildMode>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']>;
|
||||
secrets?: InputMaybe<AndroidJobSecretsInput>;
|
||||
updates?: InputMaybe<BuildUpdatesInput>;
|
||||
@@ -922,6 +921,7 @@ export type App = Project & {
|
||||
/** Classic update release channel names that have at least one build */
|
||||
buildsReleaseChannels: Array<Scalars['String']>;
|
||||
deployment?: Maybe<Deployment>;
|
||||
deploymentNew?: Maybe<DeploymentNew>;
|
||||
/** Deployments associated with this app */
|
||||
deployments: Array<Deployment>;
|
||||
description: Scalars['String'];
|
||||
@@ -1051,6 +1051,13 @@ export type AppDeploymentArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppDeploymentNewArgs = {
|
||||
channel: Scalars['String'];
|
||||
runtimeVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppDeploymentsArgs = {
|
||||
limit: Scalars['Int'];
|
||||
@@ -1800,6 +1807,12 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
workerStartedAt?: Maybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an EAS Build */
|
||||
export type BuildCanRetryArgs = {
|
||||
newMode?: InputMaybe<BuildMode>;
|
||||
};
|
||||
|
||||
export type BuildArtifact = {
|
||||
__typename?: 'BuildArtifact';
|
||||
manifestPlistUrl?: Maybe<Scalars['String']>;
|
||||
@@ -1960,6 +1973,11 @@ export type BuildMetrics = {
|
||||
buildWaitTime?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export enum BuildMode {
|
||||
Build = 'BUILD',
|
||||
Resign = 'RESIGN'
|
||||
}
|
||||
|
||||
export type BuildMutation = {
|
||||
__typename?: 'BuildMutation';
|
||||
/**
|
||||
@@ -2116,6 +2134,10 @@ export type BuildQueryByIdArgs = {
|
||||
buildId: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type BuildResignInput = {
|
||||
applicationArchiveSource?: InputMaybe<ProjectArchiveSourceInput>;
|
||||
};
|
||||
|
||||
export enum BuildResourceClass {
|
||||
AndroidDefault = 'ANDROID_DEFAULT',
|
||||
AndroidLarge = 'ANDROID_LARGE',
|
||||
@@ -2141,7 +2163,8 @@ export type BuildUpdatesInput = {
|
||||
|
||||
export enum BuildWorkflow {
|
||||
Generic = 'GENERIC',
|
||||
Managed = 'MANAGED'
|
||||
Managed = 'MANAGED',
|
||||
Unknown = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export enum CacheControlScope {
|
||||
@@ -2381,6 +2404,36 @@ export type Deployment = {
|
||||
runtimeVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeploymentBuildEdge = {
|
||||
__typename?: 'DeploymentBuildEdge';
|
||||
cursor: Scalars['String'];
|
||||
node: Build;
|
||||
};
|
||||
|
||||
export type DeploymentBuildsConnection = {
|
||||
__typename?: 'DeploymentBuildsConnection';
|
||||
edges: Array<DeploymentBuildEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
/** Represents a Deployment - a set of Builds with the same Runtime Version and Channel */
|
||||
export type DeploymentNew = {
|
||||
__typename?: 'DeploymentNew';
|
||||
builds: DeploymentBuildsConnection;
|
||||
channel: UpdateChannel;
|
||||
id: Scalars['ID'];
|
||||
runtime: Runtime;
|
||||
};
|
||||
|
||||
|
||||
/** Represents a Deployment - a set of Builds with the same Runtime Version and Channel */
|
||||
export type DeploymentNewBuildsArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type DeploymentOptions = {
|
||||
/** Max number of associated builds to return */
|
||||
buildListMaxSize?: InputMaybe<Scalars['Int']>;
|
||||
@@ -2547,7 +2600,7 @@ export type GitHubAppInstallationAccessibleRepositoriesArgs = {
|
||||
|
||||
export type GitHubAppInstallationAccessibleRepository = {
|
||||
__typename?: 'GitHubAppInstallationAccessibleRepository';
|
||||
defaultBranch: Scalars['String'];
|
||||
defaultBranch?: Maybe<Scalars['String']>;
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['Int'];
|
||||
name: Scalars['String'];
|
||||
@@ -2982,6 +3035,7 @@ export type IosJobInput = {
|
||||
/** @deprecated */
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']>;
|
||||
mode?: InputMaybe<BuildMode>;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: InputMaybe<Scalars['String']>;
|
||||
@@ -3008,10 +3062,13 @@ export type IosJobOverridesInput = {
|
||||
/** @deprecated */
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
experimental?: InputMaybe<Scalars['JSONObject']>;
|
||||
mode?: InputMaybe<BuildMode>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']>;
|
||||
resign?: InputMaybe<BuildResignInput>;
|
||||
scheme?: InputMaybe<Scalars['String']>;
|
||||
secrets?: InputMaybe<IosJobSecretsInput>;
|
||||
simulator?: InputMaybe<Scalars['Boolean']>;
|
||||
type?: InputMaybe<BuildWorkflow>;
|
||||
updates?: InputMaybe<BuildUpdatesInput>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
version?: InputMaybe<IosJobVersionInput>;
|
||||
@@ -3249,6 +3306,14 @@ export enum Order {
|
||||
Desc = 'DESC'
|
||||
}
|
||||
|
||||
export type PageInfo = {
|
||||
__typename?: 'PageInfo';
|
||||
endCursor?: Maybe<Scalars['String']>;
|
||||
hasNextPage: Scalars['Boolean'];
|
||||
hasPreviousPage: Scalars['Boolean'];
|
||||
startCursor?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type PartialManifest = {
|
||||
assets: Array<InputMaybe<PartialManifestAsset>>;
|
||||
extra?: InputMaybe<Scalars['JSONObject']>;
|
||||
@@ -3298,6 +3363,7 @@ export type ProjectArchiveSourceInput = {
|
||||
|
||||
export enum ProjectArchiveSourceType {
|
||||
Gcs = 'GCS',
|
||||
None = 'NONE',
|
||||
S3 = 'S3',
|
||||
Url = 'URL'
|
||||
}
|
||||
@@ -3347,7 +3413,9 @@ export type PublicArtifacts = {
|
||||
export type PublishUpdateGroupInput = {
|
||||
awaitingCodeSigningInfo?: InputMaybe<Scalars['Boolean']>;
|
||||
branchId: Scalars['String'];
|
||||
excludedAssets?: InputMaybe<Array<PartialManifestAsset>>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']>;
|
||||
isGitWorkingTreeDirty?: InputMaybe<Scalars['Boolean']>;
|
||||
message?: InputMaybe<Scalars['String']>;
|
||||
runtimeVersion: Scalars['String'];
|
||||
updateInfoGroup: UpdateInfoGroup;
|
||||
@@ -3630,6 +3698,14 @@ export type RootQueryUserByUsernameArgs = {
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Runtime = {
|
||||
__typename?: 'Runtime';
|
||||
app: App;
|
||||
firstBuildCreatedAt: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
version: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SecondFactorBooleanResult = {
|
||||
__typename?: 'SecondFactorBooleanResult';
|
||||
success: Scalars['Boolean'];
|
||||
@@ -4007,6 +4083,7 @@ export type Update = ActivityTimelineProjectActivity & {
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
group: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
isGitWorkingTreeDirty: Scalars['Boolean'];
|
||||
manifestFragment: Scalars['String'];
|
||||
manifestPermalink: Scalars['String'];
|
||||
message?: Maybe<Scalars['String']>;
|
||||
@@ -4331,11 +4408,15 @@ export type UserDataInput = {
|
||||
export type UserInvitation = {
|
||||
__typename?: 'UserInvitation';
|
||||
accountName: Scalars['String'];
|
||||
/** If the invite is for a personal team, the profile photo of account owner */
|
||||
accountProfilePhoto?: Maybe<Scalars['String']>;
|
||||
created: Scalars['DateTime'];
|
||||
/** Email to which this invitation was sent */
|
||||
email: Scalars['String'];
|
||||
expires: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
/** If the invite is for an organization or a personal team */
|
||||
isForOrganization: Scalars['Boolean'];
|
||||
/** Account permissions to be granted upon acceptance of this invitation */
|
||||
permissions: Array<Permission>;
|
||||
/** Role to be granted upon acceptance of this invitation */
|
||||
@@ -4404,11 +4485,13 @@ export type UserInvitationMutationResendUserInvitationArgs = {
|
||||
export type UserInvitationPublicData = {
|
||||
__typename?: 'UserInvitationPublicData';
|
||||
accountName: Scalars['String'];
|
||||
accountProfilePhoto?: Maybe<Scalars['String']>;
|
||||
created: Scalars['DateTime'];
|
||||
email: Scalars['String'];
|
||||
expires: Scalars['DateTime'];
|
||||
/** Email to which this invitation was sent */
|
||||
id: Scalars['ID'];
|
||||
isForOrganization: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type UserInvitationPublicDataQuery = {
|
||||
@@ -5017,7 +5100,7 @@ export type CreateAndroidBuildMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
|
||||
export type CreateIosBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
@@ -5027,7 +5110,15 @@ export type CreateIosBuildMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
|
||||
|
||||
export type RetryIosBuildMutationVariables = Exact<{
|
||||
buildId: Scalars['ID'];
|
||||
jobOverrides: IosJobOverridesInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type RetryIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', retryIosBuild: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
|
||||
export type CreateEnvironmentSecretForAccountMutationVariables = Exact<{
|
||||
input: CreateEnvironmentSecretInput;
|
||||
@@ -5167,7 +5258,7 @@ export type BranchesByAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> }> } } };
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }> }> } } };
|
||||
|
||||
export type ViewBranchesOnUpdateChannelQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5177,21 +5268,21 @@ export type ViewBranchesOnUpdateChannelQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }>> }> } | null } } };
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> }> } | null } } };
|
||||
|
||||
export type BuildsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
|
||||
export type BuildsWithSubmissionsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
|
||||
export type ViewBuildsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5201,7 +5292,7 @@ export type ViewBuildsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewBuildsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
|
||||
export type ViewBuildsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
|
||||
|
||||
export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5209,7 +5300,7 @@ export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, name: string, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }>> }> } | null } } };
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, name: string, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> }> } | null } } };
|
||||
|
||||
export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5218,7 +5309,7 @@ export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, name: string, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }>> }> }> } } };
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, name: string, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> }> }> } } };
|
||||
|
||||
export type EnvironmentSecretsByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5271,7 +5362,7 @@ export type ViewUpdatesByGroupQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> };
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }> };
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5282,7 +5373,7 @@ export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }>> } | null } } };
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> } | null } } };
|
||||
|
||||
export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -5292,7 +5383,7 @@ export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }>> } } };
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }>> } } };
|
||||
|
||||
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -5318,9 +5409,9 @@ export type AccountFragment = { __typename?: 'Account', id: string, name: string
|
||||
|
||||
export type AppFragment = { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, users: Array<{ __typename?: 'UserPermission', role?: Role | null, actor: { __typename?: 'Robot', id: string } | { __typename?: 'User', id: string } }> } };
|
||||
|
||||
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
|
||||
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, completedAt?: any | null, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
|
||||
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, type: EnvironmentSecretType, createdAt: any };
|
||||
|
||||
@@ -5328,9 +5419,9 @@ export type StatuspageServiceFragment = { __typename?: 'StatuspageService', id:
|
||||
|
||||
export type SubmissionFragment = { __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logsUrl?: string | null, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null };
|
||||
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } };
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null };
|
||||
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string } }> };
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, gitCommitHash?: string | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null }> };
|
||||
|
||||
export type WebhookFragment = { __typename?: 'Webhook', id: string, event: WebhookType, url: string, createdAt: any, updatedAt: any };
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
CreateIosBuildMutationVariables,
|
||||
EasBuildDeprecationInfo,
|
||||
IosJobInput,
|
||||
IosJobOverridesInput,
|
||||
RetryIosBuildMutation,
|
||||
RetryIosBuildMutationVariables,
|
||||
} from '../generated';
|
||||
import { BuildFragmentNode } from '../types/Build';
|
||||
|
||||
@@ -116,4 +119,32 @@ export const BuildMutation = {
|
||||
);
|
||||
return nullthrows(data.build?.createIosBuild);
|
||||
},
|
||||
async retryIosBuildAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
input: {
|
||||
buildId: string;
|
||||
jobOverrides: IosJobOverridesInput;
|
||||
}
|
||||
): Promise<BuildFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<RetryIosBuildMutation, RetryIosBuildMutationVariables>(
|
||||
gql`
|
||||
mutation RetryIosBuildMutation($buildId: ID!, $jobOverrides: IosJobOverridesInput!) {
|
||||
build {
|
||||
retryIosBuild(buildId: $buildId, jobOverrides: $jobOverrides) {
|
||||
id
|
||||
...BuildFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(BuildFragmentNode)}
|
||||
`,
|
||||
input,
|
||||
{ noRetry: true }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return nullthrows(data.build?.retryIosBuild);
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ChannelNotFoundError } from '../../channel/errors';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
@@ -59,7 +60,7 @@ export const ChannelQuery = {
|
||||
|
||||
const { updateChannelByName } = response.app.byId;
|
||||
if (!updateChannelByName) {
|
||||
throw new Error(`Could not find channel with the name ${channelName}`);
|
||||
throw new ChannelNotFoundError(`Could not find channel with the name ${channelName}`);
|
||||
}
|
||||
|
||||
return updateChannelByName;
|
||||
|
||||
@@ -52,6 +52,7 @@ export const BuildFragmentNode = gql`
|
||||
priority
|
||||
createdAt
|
||||
updatedAt
|
||||
completedAt
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export const UpdateFragmentNode = gql`
|
||||
runtimeVersion
|
||||
platform
|
||||
manifestFragment
|
||||
gitCommitHash
|
||||
actor {
|
||||
__typename
|
||||
id
|
||||
@@ -23,5 +24,10 @@ export const UpdateFragmentNode = gql`
|
||||
id
|
||||
name
|
||||
}
|
||||
codeSigningInfo {
|
||||
keyid
|
||||
sig
|
||||
alg
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -92,13 +92,10 @@ export function link(
|
||||
url: string,
|
||||
{ text = url, dim = true }: { text?: string; dim?: boolean } = {}
|
||||
): string {
|
||||
let output: string;
|
||||
// Links can be disabled via env variables https://github.com/jamestalmage/supports-hyperlinks/blob/master/index.js
|
||||
if (terminalLink.isSupported) {
|
||||
output = terminalLink(text, url);
|
||||
} else {
|
||||
output = `${text === url ? '' : text + ': '}${chalk.underline(url)}`;
|
||||
}
|
||||
const output = terminalLink(text, url, {
|
||||
fallback: () => (text === url ? chalk.underline(url) : `${text}: ${chalk.underline(url)}`),
|
||||
});
|
||||
return dim ? chalk.dim(output) : output;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
getApplicationIdAsync,
|
||||
} from './android/applicationId';
|
||||
import { resolveGradleBuildContextAsync } from './android/gradle';
|
||||
import { getBundleIdentifierAsync } from './ios/bundleIdentifier';
|
||||
import {
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
|
||||
getBundleIdentifierAsync,
|
||||
} from './ios/bundleIdentifier';
|
||||
import { resolveXcodeBuildContextAsync } from './ios/scheme';
|
||||
import { findApplicationTarget, resolveTargetsAsync } from './ios/target';
|
||||
import { resolveWorkflowAsync } from './workflow';
|
||||
@@ -23,15 +26,20 @@ export async function getApplicationIdentifierAsync(
|
||||
if (platform === Platform.ANDROID) {
|
||||
const profile = buildProfile as BuildProfile<Platform.ANDROID>;
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
const gradleContext = await resolveGradleBuildContextAsync(projectDir, profile);
|
||||
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(projectDir, exp, actor);
|
||||
return await ensureApplicationIdIsDefinedForManagedProjectAsync(projectDir, exp, actor);
|
||||
}
|
||||
|
||||
const gradleContext = await resolveGradleBuildContextAsync(projectDir, profile);
|
||||
return await getApplicationIdAsync(projectDir, exp, gradleContext);
|
||||
} else {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const profile = buildProfile as BuildProfile<Platform.IOS>;
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
return await ensureBundleIdentifierIsDefinedForManagedProjectAsync(projectDir, exp, actor);
|
||||
}
|
||||
|
||||
const xcodeBuildContext = await resolveXcodeBuildContextAsync(
|
||||
{ exp, projectDir, nonInteractive: false },
|
||||
profile
|
||||
|
||||
+2
-5
@@ -1,11 +1,11 @@
|
||||
import chalk from 'chalk';
|
||||
import terminalLink from 'terminal-link';
|
||||
|
||||
import { getProjectDashboardUrl } from '../build/utils/url';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppPrivacy, Role } from '../graphql/generated';
|
||||
import { AppMutation } from '../graphql/mutations/AppMutation';
|
||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||
import { link } from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { confirmAsync } from '../prompts';
|
||||
import { Actor } from '../user/User';
|
||||
@@ -83,10 +83,7 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
||||
}
|
||||
|
||||
const projectDashboardUrl = getProjectDashboardUrl(accountName, projectName);
|
||||
const projectLink = terminalLink(projectFullName, projectDashboardUrl, {
|
||||
// https://github.com/sindresorhus/terminal-link/issues/18#issuecomment-1068020361
|
||||
fallback: () => `${projectFullName} (${projectDashboardUrl})`,
|
||||
});
|
||||
const projectLink = link(projectDashboardUrl, { text: projectFullName });
|
||||
|
||||
const spinner = ora(`Creating ${chalk.bold(projectFullName)} on Expo`).start();
|
||||
try {
|
||||
|
||||
@@ -31,6 +31,7 @@ export type RawAsset = {
|
||||
contentType: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
type CollectedAssets = {
|
||||
[platform in Platform]?: {
|
||||
launchAsset: RawAsset;
|
||||
|
||||
@@ -45,7 +45,10 @@ export async function confirmAsync(
|
||||
export async function selectAsync<T>(
|
||||
message: string,
|
||||
choices: ExpoChoice<T>[],
|
||||
options?: Options
|
||||
config?: {
|
||||
options?: Options;
|
||||
warningMessageForDisabledEntries?: string;
|
||||
}
|
||||
): Promise<T> {
|
||||
const { value } = await promptAsync(
|
||||
{
|
||||
@@ -53,8 +56,9 @@ export async function selectAsync<T>(
|
||||
choices,
|
||||
name: 'value',
|
||||
type: 'select',
|
||||
warn: config?.warningMessageForDisabledEntries,
|
||||
},
|
||||
options
|
||||
config?.options ?? {}
|
||||
);
|
||||
return value ?? null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import spawnAsync, { SpawnResult } from '@expo/spawn-async';
|
||||
import glob from 'fast-glob';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getAndroidSdkRootAsync } from './sdk';
|
||||
|
||||
async function aaptAsync(...options: string[]): Promise<SpawnResult> {
|
||||
try {
|
||||
return await spawnAsync(await getAaptExecutableAsync(), options);
|
||||
} catch (error: any) {
|
||||
if (error.stderr) {
|
||||
Log.error(error.stderr);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAaptExecutableAsync(): Promise<string> {
|
||||
const sdkRoot = await getAndroidSdkRootAsync();
|
||||
if (!sdkRoot) {
|
||||
Log.debug('Failed to resolve the Android SDK path, falling back to global aapt executable');
|
||||
return 'aapt';
|
||||
}
|
||||
const aaptPaths = await glob(
|
||||
path.posix.join('build-tools/**', process.platform === 'win32' ? 'aapt.exe' : 'aapt'),
|
||||
{ cwd: sdkRoot, absolute: true }
|
||||
);
|
||||
|
||||
if (aaptPaths.length === 0) {
|
||||
throw new Error('Failed to resolve the Android aapt path');
|
||||
}
|
||||
const sorted = aaptPaths.sort();
|
||||
return sorted[sorted.length - 1];
|
||||
}
|
||||
|
||||
export async function getAptParametersAsync(
|
||||
appPath: string
|
||||
): Promise<{ packageName: string; activityName: string }> {
|
||||
const { stdout } = await aaptAsync('dump', 'badging', appPath);
|
||||
|
||||
const packageNameMatch = stdout.match(/package: name='([^']+)'/);
|
||||
if (!packageNameMatch) {
|
||||
throw new Error(`Could not read package name from ${appPath}`);
|
||||
}
|
||||
|
||||
// get activity name
|
||||
const activityNameMatch = stdout.match(/launchable-activity: name='([^']+)'/);
|
||||
if (!activityNameMatch) {
|
||||
throw new Error(`Could not read activity name from ${appPath}`);
|
||||
}
|
||||
|
||||
return {
|
||||
packageName: packageNameMatch[1],
|
||||
activityName: activityNameMatch[1],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import spawnAsync, { SpawnResult } from '@expo/spawn-async';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../../log';
|
||||
import { truthy } from '../../utils/expodash/filter';
|
||||
import { sleepAsync } from '../../utils/promise';
|
||||
import { getAndroidSdkRootAsync } from './sdk';
|
||||
|
||||
export interface AndroidEmulator {
|
||||
pid?: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const BEGINNING_OF_ADB_ERROR_MESSAGE = 'error: ';
|
||||
|
||||
export async function adbAsync(...args: string[]): Promise<SpawnResult> {
|
||||
const adbExecutable = await getAdbExecutableAsync();
|
||||
try {
|
||||
return await spawnAsync(adbExecutable, args);
|
||||
} catch (error: any) {
|
||||
let errorMessage = (error.stderr || error.stdout || error.message).trim();
|
||||
if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) {
|
||||
errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length);
|
||||
}
|
||||
error.message = errorMessage;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAdbExecutableAsync(): Promise<string> {
|
||||
const sdkRoot = await getAndroidSdkRootAsync();
|
||||
if (!sdkRoot) {
|
||||
Log.debug('Failed to resolve the Android SDK path, falling back to global adb executable');
|
||||
return 'adb';
|
||||
}
|
||||
|
||||
return path.join(sdkRoot, 'platform-tools/adb');
|
||||
}
|
||||
|
||||
export function sanitizeAdbDeviceName(deviceName: string): string | undefined {
|
||||
return deviceName.trim().split(/[\r\n]+/)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Emulator name for an emulator ID, this can be used to determine if an emulator is booted.
|
||||
*
|
||||
* @param devicePid a value like `emulator-5554` from `abd devices`
|
||||
*/
|
||||
export async function getAdbNameForDeviceIdAsync(emulatorPid: string): Promise<string | null> {
|
||||
const { stdout } = await adbAsync('-s', emulatorPid, 'emu', 'avd', 'name');
|
||||
|
||||
if (stdout.match(/could not connect to TCP port .*: Connection refused/)) {
|
||||
// Can also occur when the emulator does not exist.
|
||||
throw new Error(`Emulator not found: ${stdout}`);
|
||||
}
|
||||
|
||||
return sanitizeAdbDeviceName(stdout) ?? null;
|
||||
}
|
||||
|
||||
// TODO: This is very expensive for some operations.
|
||||
export async function getRunningEmulatorsAsync(): Promise<AndroidEmulator[]> {
|
||||
const { stdout } = await adbAsync('devices', '-l');
|
||||
|
||||
const splitItems = stdout.trim().split(os.EOL);
|
||||
|
||||
const attachedDevices: {
|
||||
pid: string;
|
||||
type: string;
|
||||
}[] = splitItems
|
||||
// First line is `"List of devices attached"`, remove it
|
||||
.slice(1, splitItems.length)
|
||||
.map(line => {
|
||||
// unauthorized: ['FA8251A00719', 'unauthorized', 'usb:338690048X', 'transport_id:5']
|
||||
// authorized: ['FA8251A00719', 'device', 'usb:336592896X', 'product:walleye', 'model:Pixel_2', 'device:walleye', 'transport_id:4']
|
||||
// emulator: ['emulator-5554', 'offline', 'transport_id:1']
|
||||
const [pid] = line.split(' ').filter(truthy);
|
||||
const type = line.includes('emulator') ? 'emulator' : 'device';
|
||||
return { pid, type };
|
||||
})
|
||||
.filter(({ pid, type }) => !!pid && type === 'emulator');
|
||||
|
||||
const devicePromises = attachedDevices.map<Promise<AndroidEmulator>>(async ({ pid }) => {
|
||||
const name = (await getAdbNameForDeviceIdAsync(pid)) ?? '';
|
||||
return {
|
||||
pid,
|
||||
name,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(devicePromises);
|
||||
}
|
||||
|
||||
export async function getFirstRunningEmulatorAsync(): Promise<AndroidEmulator | null> {
|
||||
const emulators = await getRunningEmulatorsAsync();
|
||||
return emulators[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if emulator is booted
|
||||
*
|
||||
* @param emulatorPid
|
||||
*/
|
||||
export async function isEmulatorBootedAsync(emulatorPid: string): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await adbAsync('-s', emulatorPid, 'shell', 'getprop', 'sys.boot_completed');
|
||||
if (stdout.trim() === '1') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForEmulatorToBeBootedAsync(
|
||||
maxWaitTimeMs: number,
|
||||
intervalMs: number
|
||||
): Promise<AndroidEmulator> {
|
||||
Log.newLine();
|
||||
Log.log('Waiting for the Android emulator to start...');
|
||||
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < maxWaitTimeMs) {
|
||||
const emulator = await getFirstRunningEmulatorAsync();
|
||||
if (emulator?.pid && (await isEmulatorBootedAsync(emulator.pid))) {
|
||||
return emulator;
|
||||
}
|
||||
await sleepAsync(intervalMs);
|
||||
}
|
||||
throw new Error('Timed out waiting for the Android emulator to start.');
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import spawnAsync, { SpawnResult } from '@expo/spawn-async';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { truthy } from '../../utils/expodash/filter';
|
||||
import {
|
||||
AndroidEmulator,
|
||||
adbAsync,
|
||||
getFirstRunningEmulatorAsync,
|
||||
isEmulatorBootedAsync,
|
||||
waitForEmulatorToBeBootedAsync,
|
||||
} from './adb';
|
||||
import { getAndroidSdkRootAsync } from './sdk';
|
||||
|
||||
export const EMULATOR_MAX_WAIT_TIMEOUT_MS = 60 * 1000 * 3;
|
||||
|
||||
export async function getEmulatorExecutableAsync(): Promise<string> {
|
||||
const sdkRoot = await getAndroidSdkRootAsync();
|
||||
if (sdkRoot) {
|
||||
return path.join(sdkRoot, 'emulator', 'emulator');
|
||||
}
|
||||
|
||||
return 'emulator';
|
||||
}
|
||||
|
||||
async function emulatorAsync(...options: string[]): Promise<SpawnResult> {
|
||||
const emulatorExecutable = await getEmulatorExecutableAsync();
|
||||
try {
|
||||
return await spawnAsync(emulatorExecutable, options);
|
||||
} catch (error: any) {
|
||||
if (error.stderr) {
|
||||
Log.error(error.stderr);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAvaliableAndroidEmulatorsAsync(): Promise<AndroidEmulator[]> {
|
||||
try {
|
||||
const { stdout } = await emulatorAsync('-list-avds');
|
||||
|
||||
return stdout
|
||||
.split(os.EOL)
|
||||
.filter(truthy)
|
||||
.map(name => ({
|
||||
name,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Start an Android device and wait until it is booted. */
|
||||
async function bootEmulatorAsync(
|
||||
emulator: AndroidEmulator,
|
||||
{
|
||||
timeout = EMULATOR_MAX_WAIT_TIMEOUT_MS,
|
||||
interval = 1000,
|
||||
}: {
|
||||
/** Time in milliseconds to wait before asserting a timeout error. */
|
||||
timeout?: number;
|
||||
interval?: number;
|
||||
} = {}
|
||||
): Promise<AndroidEmulator> {
|
||||
Log.newLine();
|
||||
Log.log(`Opening emulator ${chalk.bold(emulator.name)}`);
|
||||
|
||||
const emulatorExecutable = await getEmulatorExecutableAsync();
|
||||
|
||||
// Start a process to open an emulator
|
||||
const emulatorProcess = spawnAsync(emulatorExecutable, [`@${emulator.name}`], {
|
||||
stdio: 'ignore',
|
||||
detached: true,
|
||||
});
|
||||
|
||||
// we don't want to wait for the emulator process to exit before we can finish `eas build:run` command
|
||||
// https://github.com/expo/eas-cli/pull/1485#discussion_r1007935871
|
||||
emulatorProcess.child.unref();
|
||||
|
||||
return await waitForEmulatorToBeBootedAsync(timeout, interval);
|
||||
}
|
||||
|
||||
export async function selectEmulatorAsync(): Promise<AndroidEmulator> {
|
||||
const runningEmulator = await getFirstRunningEmulatorAsync();
|
||||
|
||||
if (runningEmulator) {
|
||||
Log.newLine();
|
||||
Log.log(`Using open emulator: ${chalk.bold(runningEmulator.name)}`);
|
||||
|
||||
return runningEmulator;
|
||||
}
|
||||
|
||||
const emulators = await getAvaliableAndroidEmulatorsAsync();
|
||||
|
||||
Log.newLine();
|
||||
const { selectedEmulator } = await promptAsync({
|
||||
type: 'select',
|
||||
message: `Select an emulator to run your app on`,
|
||||
name: 'selectedEmulator',
|
||||
choices: emulators.map(emulator => ({
|
||||
title: emulator.name,
|
||||
value: emulator,
|
||||
})),
|
||||
});
|
||||
|
||||
return selectedEmulator;
|
||||
}
|
||||
|
||||
export async function ensureEmulatorBootedAsync(
|
||||
emulator: AndroidEmulator
|
||||
): Promise<AndroidEmulator> {
|
||||
if (!emulator.pid || !(await isEmulatorBootedAsync(emulator.pid))) {
|
||||
return await bootEmulatorAsync(emulator);
|
||||
}
|
||||
|
||||
return emulator;
|
||||
}
|
||||
|
||||
export async function installAppAsync(
|
||||
emulator: AndroidEmulator,
|
||||
apkFilePath: string
|
||||
): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log('Installing your app...');
|
||||
|
||||
assert(emulator.pid);
|
||||
await adbAsync('-s', emulator.pid, 'install', '-r', '-d', apkFilePath);
|
||||
|
||||
Log.succeed('Successfully installed your app!');
|
||||
}
|
||||
|
||||
export async function startAppAsync(
|
||||
emulator: AndroidEmulator,
|
||||
packageName: string,
|
||||
activityName: string
|
||||
): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log('Starting your app...');
|
||||
|
||||
assert(emulator.pid);
|
||||
await adbAsync(
|
||||
'-s',
|
||||
emulator.pid,
|
||||
'shell',
|
||||
'am',
|
||||
'start',
|
||||
'-a',
|
||||
'android.intent.action.MAIN',
|
||||
'-f',
|
||||
'0x20000000', // FLAG_ACTIVITY_SINGLE_TOP -- If set, the activity will not be launched if it is already running at the top of the history stack.
|
||||
'-n',
|
||||
`${packageName}/${activityName}`
|
||||
);
|
||||
|
||||
Log.succeed('Successfully started your app!');
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
import Log from '../../log';
|
||||
import { getAptParametersAsync } from './aapt';
|
||||
import * as emulator from './emulator';
|
||||
import { assertExecutablesExistAsync } from './systemRequirements';
|
||||
|
||||
export async function runAppOnAndroidEmulatorAsync(_appPath: string): Promise<void> {
|
||||
Log.error('Not implemented!');
|
||||
export async function runAppOnAndroidEmulatorAsync(appPath: string): Promise<void> {
|
||||
await assertExecutablesExistAsync();
|
||||
const selectedEmulator = await emulator.selectEmulatorAsync();
|
||||
const bootedEmulator = await emulator.ensureEmulatorBootedAsync(selectedEmulator);
|
||||
await emulator.installAppAsync(bootedEmulator, appPath);
|
||||
const { packageName, activityName } = await getAptParametersAsync(appPath);
|
||||
await emulator.startAppAsync(bootedEmulator, packageName, activityName);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { pathExists } from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export const ANDROID_DEFAULT_LOCATION: Readonly<Partial<Record<NodeJS.Platform, string>>> = {
|
||||
darwin: path.join(os.homedir(), 'Library', 'Android', 'sdk'),
|
||||
linux: path.join(os.homedir(), 'Android', 'Sdk'),
|
||||
win32: path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
|
||||
};
|
||||
|
||||
const ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM = ANDROID_DEFAULT_LOCATION[process.platform];
|
||||
|
||||
export async function getAndroidSdkRootAsync(): Promise<string | null> {
|
||||
if (process.env.ANDROID_HOME && (await pathExists(process.env.ANDROID_HOME))) {
|
||||
return process.env.ANDROID_HOME;
|
||||
} else if (process.env.ANDROID_SDK_ROOT && (await pathExists(process.env.ANDROID_SDK_ROOT))) {
|
||||
return process.env.ANDROID_SDK_ROOT;
|
||||
} else if (
|
||||
ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM &&
|
||||
(await pathExists(ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM))
|
||||
) {
|
||||
return ANDROID_DEFAULT_LOCATION_FOR_CURRENT_PLATFORM;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getAaptExecutableAsync } from './aapt';
|
||||
import { getAdbExecutableAsync } from './adb';
|
||||
import { getEmulatorExecutableAsync } from './emulator';
|
||||
|
||||
async function assertExecutableExistsAsync(executable: string, options?: string[]): Promise<void> {
|
||||
try {
|
||||
await spawnAsync(executable, options);
|
||||
} catch (err: any) {
|
||||
throw new Error(
|
||||
`${chalk.bold(
|
||||
executable
|
||||
)} executable doesn't seem to work. Please make sure Android Studio is installed on your device and ${chalk.bold(
|
||||
'ANDROID_HOME'
|
||||
)} or ${chalk.bold('ANDROID_SDK_ROOT')} env variables are set.
|
||||
${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertExecutablesExistAsync(): Promise<void> {
|
||||
await assertExecutableExistsAsync(await getAdbExecutableAsync(), ['--version']);
|
||||
await assertExecutableExistsAsync(await getEmulatorExecutableAsync(), ['-list-avds']);
|
||||
await assertExecutableExistsAsync(await getAaptExecutableAsync(), ['version']);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export async function selectSimulatorAsync(): Promise<IosSimulator> {
|
||||
message: `Select a simulator to run your app on`,
|
||||
name: 'selectedSimulator',
|
||||
choices: simulators.map(simulator => ({
|
||||
title: `ios ${simulator.osVersion} ${simulator.name}`,
|
||||
title: `iOS ${simulator.osVersion} ${simulator.name}`,
|
||||
value: simulator,
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { AppPlatform, BuildFragment, BuildStatus, DistributionType } from '../graphql/generated';
|
||||
|
||||
function isAab(build: BuildFragment): boolean {
|
||||
return build.artifacts?.applicationArchiveUrl?.endsWith('.aab') ?? false;
|
||||
}
|
||||
|
||||
function didArtifactsExpire(build: BuildFragment): boolean {
|
||||
return new Date().getTime() - new Date(build.completedAt).getTime() > 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
}
|
||||
|
||||
export function isRunnableOnSimulatorOrEmulator(build: BuildFragment): boolean {
|
||||
return (
|
||||
build.status === BuildStatus.Finished &&
|
||||
!!build.artifacts?.applicationArchiveUrl &&
|
||||
((build.platform === AppPlatform.Ios && build.distribution === DistributionType.Simulator) ||
|
||||
(build.platform === AppPlatform.Android && !isAab(build))) &&
|
||||
!didArtifactsExpire(build)
|
||||
);
|
||||
}
|
||||
@@ -320,23 +320,22 @@ function formatBuildChoice(build: BuildFragment, expiryDate: Date): prompts.Choi
|
||||
const { id, updatedAt, runtimeVersion, buildProfile, gitCommitHash, gitCommitMessage, channel } =
|
||||
build;
|
||||
|
||||
const formatValue = (field?: string | null): string =>
|
||||
field ? chalk.bold(field) : chalk.dim('Unknown');
|
||||
const formatValue = (field?: string | null): string => (field ? chalk.bold(field) : 'Unknown');
|
||||
|
||||
const buildDate = new Date(updatedAt);
|
||||
|
||||
const formattedCommitData =
|
||||
gitCommitHash && gitCommitMessage
|
||||
? `${chalk.dim(gitCommitHash.slice(0, 7))} "${chalk.bold(gitCommitMessage)}"`
|
||||
? `${gitCommitHash.slice(0, 7)} "${chalk.bold(gitCommitMessage)}"`
|
||||
: 'Unknown';
|
||||
|
||||
const title = `ID: ${chalk.dim(id)} (${chalk.dim(`${fromNow(buildDate)} ago`)})`;
|
||||
const title = `${chalk.bold(`ID:`)} ${id} (${chalk.bold(`${fromNow(buildDate)} ago`)})`;
|
||||
|
||||
const description = [
|
||||
`\tProfile: ${formatValue(buildProfile)}`,
|
||||
`\tChannel: ${formatValue(channel)}`,
|
||||
`\tRuntime version: ${formatValue(runtimeVersion)}`,
|
||||
`\tCommit: ${formattedCommitData}`,
|
||||
`\t${chalk.bold(`Profile:`)} ${formatValue(buildProfile)}`,
|
||||
`\t${chalk.bold(`Channel:`)} ${formatValue(channel)}`,
|
||||
`\t${chalk.bold(`Runtime version:`)} ${formatValue(runtimeVersion)}`,
|
||||
`\t${chalk.bold(`Commit:`)} ${formattedCommitData}`,
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import chalk from 'chalk';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { getExpoWebsiteBaseUrl } from '../../api';
|
||||
import { SubmissionFragment } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import Log, { link } from '../../log';
|
||||
import { appPlatformDisplayNames } from '../../platform';
|
||||
|
||||
export function printSubmissionDetailsUrls(submissions: SubmissionFragment[]): void {
|
||||
if (submissions.length === 1) {
|
||||
const [submission] = submissions;
|
||||
Log.log(`Submission details: ${chalk.underline(getSubmissionDetailsUrl(submission))}`);
|
||||
Log.log(`Submission details: ${link(getSubmissionDetailsUrl(submission))}`);
|
||||
} else {
|
||||
submissions.forEach(submission => {
|
||||
Log.log(
|
||||
`${appPlatformDisplayNames[submission.platform]} submission details: ${chalk.underline(
|
||||
`${appPlatformDisplayNames[submission.platform]} submission details: ${link(
|
||||
getSubmissionDetailsUrl(submission)
|
||||
)}`
|
||||
);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { UpdateBranch, UpdateChannel } from '../../graphql/generated';
|
||||
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
|
||||
import { getBranchNameFromChannelNameAsync } from '../getBranchNameFromChannelNameAsync';
|
||||
|
||||
jest.mock('../../graphql/queries/ChannelQuery');
|
||||
jest.mock('../../graphql/queries/BranchQuery');
|
||||
|
||||
describe(getBranchNameFromChannelNameAsync, () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test('returns branch name when channel exists and has one connected branch', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(ChannelQuery.viewUpdateChannelAsync).mockImplementationOnce(
|
||||
async () =>
|
||||
mockUpdateChannel({
|
||||
channelName: 'test-channel-name',
|
||||
branchNames: ['test-branch-name'],
|
||||
}) as any
|
||||
);
|
||||
|
||||
const result = await getBranchNameFromChannelNameAsync(
|
||||
graphqlClient,
|
||||
'test-project-id',
|
||||
'channel-name'
|
||||
);
|
||||
|
||||
expect(result).toBe('test-branch-name');
|
||||
});
|
||||
|
||||
test('errors when no branch is connected to channel', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(ChannelQuery.viewUpdateChannelAsync).mockImplementationOnce(
|
||||
async () =>
|
||||
mockUpdateChannel({
|
||||
channelName: 'test-channel-name',
|
||||
}) as any
|
||||
);
|
||||
|
||||
expect(
|
||||
getBranchNameFromChannelNameAsync(graphqlClient, 'test-project-id', 'test-channel-name')
|
||||
).rejects.toThrow(
|
||||
"Channel has no branches associated with it. Run 'eas channel:edit' to map a branch"
|
||||
);
|
||||
});
|
||||
|
||||
test('errors when more than one branch is connected to channel', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(ChannelQuery.viewUpdateChannelAsync).mockImplementationOnce(
|
||||
async () =>
|
||||
mockUpdateChannel({
|
||||
channelName: 'test-channel-name',
|
||||
branchNames: ['test-branch-name', 'test-branch-name-2'],
|
||||
}) as any
|
||||
);
|
||||
|
||||
expect(
|
||||
getBranchNameFromChannelNameAsync(graphqlClient, 'test-project-id', 'test-channel-name')
|
||||
).rejects.toThrow(
|
||||
"Channel has multiple branches associated with it. Instead, use '--branch' instead of '--channel'"
|
||||
);
|
||||
});
|
||||
|
||||
test('creates channel and branch when channel does not exist, and returns branch name', async () => {
|
||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||
jest.mocked(ChannelQuery.viewUpdateChannelAsync).mockImplementationOnce(
|
||||
async () =>
|
||||
mockUpdateChannel({
|
||||
channelName: 'test-channel-name',
|
||||
branchNames: ['test-branch-name'],
|
||||
}) as any
|
||||
);
|
||||
|
||||
const result = await getBranchNameFromChannelNameAsync(
|
||||
graphqlClient,
|
||||
'test-project-id',
|
||||
'test-channel-name'
|
||||
);
|
||||
|
||||
expect(result).toBe('test-branch-name');
|
||||
});
|
||||
});
|
||||
|
||||
function mockUpdateChannel({
|
||||
channelName,
|
||||
branchNames,
|
||||
}: {
|
||||
channelName?: string;
|
||||
branchNames?: string[];
|
||||
}): UpdateChannel {
|
||||
return {
|
||||
id: 'a2a1fa12-9d6a-433a-a432-49c64ef8439f',
|
||||
name: channelName ?? 'default-channel-name',
|
||||
createdAt: '2022-12-07T02:24:29.786Z',
|
||||
appId: '123',
|
||||
updatedAt: '2022-12-07T02:24:29.786Z',
|
||||
branchMapping:
|
||||
'{"data":[{"branchId":"f9dc4dbb-663f-4a19-8cf2-a783b484d2db","branchMappingLogic":"true"}],"version":0}',
|
||||
updateBranches: branchNames ? mockUpdateBranches(branchNames) : [],
|
||||
__typename: 'UpdateChannel',
|
||||
};
|
||||
}
|
||||
|
||||
function mockUpdateBranches(branchNames: string[]): UpdateBranch[] {
|
||||
return branchNames.map(branchName => ({
|
||||
id: 'f9dc4dbb-663f-4a19-8cf2-a783b484d2db',
|
||||
name: branchName ?? 'default-branch-name',
|
||||
appId: '123',
|
||||
createdAt: '2022-12-07T02:24:29.786Z',
|
||||
updatedAt: '2022-12-07T02:24:29.786Z',
|
||||
updates: [],
|
||||
updateGroups: [
|
||||
[
|
||||
{
|
||||
id: '8503286d-4b18-4b38-a0f7-2b9ae09531a0',
|
||||
group: 'd715bc20-76b5-477b-a060-3f8184aae87a',
|
||||
message: 'my message',
|
||||
createdAt: '2022-12-07T02:24:43.487Z',
|
||||
runtimeVersion: '1.0.0',
|
||||
platform: 'ios',
|
||||
manifestFragment: '...',
|
||||
activityTimestamp: '2022-12-07T02:24:43.487Z',
|
||||
branchId: '',
|
||||
awaitingCodeSigningInfo: false,
|
||||
isGitWorkingTreeDirty: false,
|
||||
manifestPermalink: '...',
|
||||
updatedAt: '2022-12-07T02:24:43.487Z',
|
||||
gitCommitHash: 'abc5baf77eee821b800af97879d19cc3132b46a7',
|
||||
actor: {
|
||||
__typename: 'User' as const,
|
||||
id: 'f42b73a3-ce6c-4059-a825-e5ddafa022f7',
|
||||
username: 'jonexpo',
|
||||
} as any,
|
||||
branch: {
|
||||
id: 'f9dc4dbb-663f-4a19-8cf2-a783b484d2db',
|
||||
name: 'production3',
|
||||
appId: '123',
|
||||
createdAt: '2022-12-07T02:24:29.786Z',
|
||||
updatedAt: '2022-12-07T02:24:29.786Z',
|
||||
updateGroups: [],
|
||||
updates: [],
|
||||
__typename: 'UpdateBranch' as const,
|
||||
},
|
||||
codeSigningInfo: null,
|
||||
__typename: 'Update' as const,
|
||||
},
|
||||
],
|
||||
],
|
||||
__typename: 'UpdateBranch' as const,
|
||||
}));
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { modifyConfigAsync } from '@expo/config';
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonAccessor } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { getEASUpdateURL } from '../api';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
@@ -16,7 +18,7 @@ import { resolveWorkflowPerPlatformAsync } from '../project/workflow';
|
||||
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from './android/UpdatesModule';
|
||||
import { syncUpdatesConfigurationAsync as syncIosUpdatesConfigurationAsync } from './ios/UpdatesModule';
|
||||
|
||||
export const DEFAULT_MANAGED_RUNTIME_VERSION = { policy: 'sdkVersion' } as const;
|
||||
export const DEFAULT_MANAGED_RUNTIME_VERSION = { policy: 'appVersion' } as const;
|
||||
export const DEFAULT_BARE_RUNTIME_VERSION = '1.0.0' as const;
|
||||
|
||||
function getDefaultRuntimeVersion(workflow: Workflow): NonNullable<ExpoConfig['runtimeVersion']> {
|
||||
@@ -225,7 +227,7 @@ function warnEASUpdatesManualConfig({
|
||||
/**
|
||||
* Make sure that the current `app.json` configuration for EAS Updates is set natively.
|
||||
*/
|
||||
async function ensureEASUpdatesIsConfiguredNativelyAsync(
|
||||
async function ensureEASUpdateIsConfiguredNativelyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
exp,
|
||||
@@ -252,6 +254,67 @@ async function ensureEASUpdatesIsConfiguredNativelyAsync(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure EAS Build profiles are configured to work with EAS Update by adding channels to build profiles.
|
||||
*/
|
||||
|
||||
export async function ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir: string): Promise<void> {
|
||||
const easJsonPath = EasJsonAccessor.formatEasJsonPath(projectDir);
|
||||
|
||||
if (!(await fs.pathExists(easJsonPath))) {
|
||||
Log.warn(
|
||||
`EAS Build is not configured. If you'd like to use EAS Build with EAS Update, run ${chalk.bold(
|
||||
'eas build:configure'
|
||||
)}, then re-run ${chalk.bold('eas update:configure')} to configure ${chalk.bold(
|
||||
'eas.json'
|
||||
)} with EAS Update.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const easJsonAccessor = new EasJsonAccessor(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
const easBuildProfilesWithChannels = Object.keys(easJsonRawObject.build).reduce(
|
||||
(acc, profileNameKey) => {
|
||||
const buildProfile = easJsonRawObject.build[profileNameKey];
|
||||
const isNotAlreadyConfigured = !buildProfile.channel && !buildProfile.releaseChannel;
|
||||
|
||||
if (isNotAlreadyConfigured) {
|
||||
return {
|
||||
...acc,
|
||||
[profileNameKey]: {
|
||||
...buildProfile,
|
||||
channel: profileNameKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[profileNameKey]: {
|
||||
...easJsonRawObject.build[profileNameKey],
|
||||
},
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
...easJsonRawObject,
|
||||
build: easBuildProfilesWithChannels,
|
||||
};
|
||||
});
|
||||
|
||||
await easJsonAccessor.writeAsync();
|
||||
Log.withTick(`Configured ${chalk.bold('eas.json')}.`);
|
||||
} catch (error) {
|
||||
Log.error(`We were not able to configure ${chalk.bold('eas.json')}. Error: ${error}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure EAS Update is fully configured in the current project.
|
||||
* This goes over a checklist and performs the following checks or changes:
|
||||
@@ -261,7 +324,7 @@ async function ensureEASUpdatesIsConfiguredNativelyAsync(
|
||||
* - Sets `updates.url` if not set
|
||||
* - Ensure latest changes are reflected in the native config, if any
|
||||
*/
|
||||
export async function ensureEASUpdatesIsConfiguredAsync(
|
||||
export async function ensureEASUpdateIsConfiguredAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
exp: expWithoutUpdates,
|
||||
@@ -301,7 +364,7 @@ export async function ensureEASUpdatesIsConfiguredAsync(
|
||||
});
|
||||
|
||||
if (projectChanged || !hasExpoUpdates) {
|
||||
await ensureEASUpdatesIsConfiguredNativelyAsync(graphqlClient, {
|
||||
await ensureEASUpdateIsConfiguredNativelyAsync(graphqlClient, {
|
||||
exp: expWithUpdates,
|
||||
projectDir,
|
||||
projectId,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ensureBranchExistsAsync } from '../branch/queries';
|
||||
import { ChannelNotFoundError } from '../channel/errors';
|
||||
import { createChannelOnAppAsync } from '../channel/queries';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { ChannelQuery } from '../graphql/queries/ChannelQuery';
|
||||
|
||||
export async function getBranchNameFromChannelNameAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
channelName: string
|
||||
): Promise<string> {
|
||||
let branchName;
|
||||
|
||||
try {
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName,
|
||||
});
|
||||
|
||||
if (channel.updateBranches.length === 1) {
|
||||
branchName = channel.updateBranches[0].name;
|
||||
} else if (channel.updateBranches.length === 0) {
|
||||
throw new Error(
|
||||
"Channel has no branches associated with it. Run 'eas channel:edit' to map a branch"
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Channel has multiple branches associated with it. Instead, use '--branch' instead of '--channel'`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!(error instanceof ChannelNotFoundError)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName: channelName,
|
||||
});
|
||||
const {
|
||||
updateChannel: { createUpdateChannelForApp: newChannel },
|
||||
} = await createChannelOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName,
|
||||
branchId,
|
||||
});
|
||||
|
||||
if (!newChannel) {
|
||||
throw new Error(
|
||||
`Could not create channel with name ${channelName} on project with id ${projectId}`
|
||||
);
|
||||
}
|
||||
|
||||
branchName = channelName;
|
||||
}
|
||||
|
||||
return branchName;
|
||||
}
|
||||
@@ -126,7 +126,9 @@ export async function selectUpdateGroupOnBranchAsync(
|
||||
}),
|
||||
promptOptions: {
|
||||
title: 'Load more update groups?',
|
||||
createDisplayTextForSelectionPromptListItem: updateGroup => formatUpdateTitle(updateGroup[0]),
|
||||
makePartialChoiceObject: updateGroup => ({
|
||||
title: formatUpdateTitle(updateGroup[0]),
|
||||
}),
|
||||
getIdentifierForQueryItem: updateGroup => updateGroup[0].group,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ describe(paginatedQueryWithSelectPromptAsync.name, () => {
|
||||
promptOptions: {
|
||||
title: '',
|
||||
getIdentifierForQueryItem: item => item.id,
|
||||
createDisplayTextForSelectionPromptListItem: item => 'item: ' + item.value,
|
||||
makePartialChoiceObject: item => ({ title: 'item: ' + item.value }),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@ describe(paginatedQueryWithSelectPromptAsync.name, () => {
|
||||
promptOptions: {
|
||||
title: '',
|
||||
getIdentifierForQueryItem: item => item.id,
|
||||
createDisplayTextForSelectionPromptListItem: item => item.value,
|
||||
makePartialChoiceObject: item => ({ title: item.value }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import cliProgress from 'cli-progress';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { Stream } from 'stream';
|
||||
import { extract } from 'tar';
|
||||
import { promisify } from 'util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import fetch, { RequestInit } from '../fetch';
|
||||
import fetch, { RequestInit, Response } from '../fetch';
|
||||
import { AppPlatform } from '../graphql/generated';
|
||||
import Log from '../log';
|
||||
import { formatBytes } from './files';
|
||||
import { getTmpDirectory } from './paths';
|
||||
import { ProgressHandler, createProgressTracker } from './progress';
|
||||
|
||||
const pipeline = promisify(Stream.pipeline);
|
||||
|
||||
type ProgressCallback = (progress: number, total: number, loaded: number) => void;
|
||||
|
||||
function wrapFetchWithProgress() {
|
||||
return async (url: string, init: RequestInit, onProgressCallback: ProgressCallback) => {
|
||||
function wrapFetchWithProgress(): (
|
||||
url: string,
|
||||
init: RequestInit,
|
||||
progressHandler: ProgressHandler
|
||||
) => Promise<Response> {
|
||||
let didProgressBarFinish = false;
|
||||
return async (url: string, init: RequestInit, progressHandler: ProgressHandler) => {
|
||||
const response = await fetch(url, init);
|
||||
|
||||
if (response.ok) {
|
||||
@@ -40,7 +45,15 @@ function wrapFetchWithProgress() {
|
||||
|
||||
const progress = length / total;
|
||||
|
||||
onProgressCallback(progress, total, length);
|
||||
if (!didProgressBarFinish) {
|
||||
progressHandler({
|
||||
progress: { total, percent: progress, transferred: length },
|
||||
isComplete: total === length,
|
||||
});
|
||||
if (total === length) {
|
||||
didProgressBarFinish = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
response.body.on('data', chunk => {
|
||||
@@ -56,72 +69,96 @@ function wrapFetchWithProgress() {
|
||||
};
|
||||
}
|
||||
|
||||
async function downloadFileWithProgressBarAsync(
|
||||
async function downloadFileWithProgressTrackerAsync(
|
||||
url: string,
|
||||
outputPath: string,
|
||||
infoMessage?: string
|
||||
progressTrackerMessage: string | ((ratio: number, total: number) => string),
|
||||
progressTrackerCompletedMessage: string
|
||||
): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log(infoMessage ? infoMessage : `Downloading file from ${url}...`);
|
||||
|
||||
const downloadProgressBar = new cliProgress.SingleBar(
|
||||
{ format: '|{bar}|' },
|
||||
cliProgress.Presets.rect
|
||||
);
|
||||
try {
|
||||
const response = await wrapFetchWithProgress()(
|
||||
url,
|
||||
{
|
||||
timeout: 1000 * 60 * 5, // 5 minutes
|
||||
},
|
||||
createProgressTracker({
|
||||
message: progressTrackerMessage,
|
||||
completedMessage: progressTrackerCompletedMessage,
|
||||
})
|
||||
);
|
||||
|
||||
let downloadProgressBarStarted = false;
|
||||
const response = await wrapFetchWithProgress()(
|
||||
url,
|
||||
{
|
||||
timeout: 1000 * 60 * 5, // 5 minutes
|
||||
},
|
||||
(_progress: number, total: number, loaded: number): void => {
|
||||
if (!downloadProgressBarStarted) {
|
||||
downloadProgressBar.start(total, loaded);
|
||||
downloadProgressBarStarted = true;
|
||||
} else if (loaded < total) {
|
||||
downloadProgressBar.update(loaded);
|
||||
} else {
|
||||
downloadProgressBar.stop();
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download file from ${url}`);
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download file from ${url}`);
|
||||
await pipeline(response.body, fs.createWriteStream(outputPath));
|
||||
} catch (error: any) {
|
||||
if (await fs.pathExists(outputPath)) {
|
||||
await fs.remove(outputPath);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
await pipeline(response.body, fs.createWriteStream(outputPath));
|
||||
}
|
||||
|
||||
export async function downloadAndExtractAppAsync(
|
||||
async function maybeCacheAppAsync(appPath: string, cachedAppPath?: string): Promise<string> {
|
||||
if (cachedAppPath) {
|
||||
await fs.ensureDir(path.dirname(cachedAppPath));
|
||||
await fs.move(appPath, cachedAppPath);
|
||||
return cachedAppPath;
|
||||
}
|
||||
return appPath;
|
||||
}
|
||||
|
||||
export async function downloadAndMaybeExtractAppAsync(
|
||||
url: string,
|
||||
applicationExtension: string
|
||||
platform: AppPlatform,
|
||||
cachedAppPath?: string
|
||||
): Promise<string> {
|
||||
const outputDir = path.join(getTmpDirectory(), uuidv4());
|
||||
await fs.promises.mkdir(outputDir, { recursive: true });
|
||||
|
||||
const tmpArchivePathDir = path.join(getTmpDirectory(), uuidv4());
|
||||
await fs.promises.mkdir(tmpArchivePathDir, { recursive: true });
|
||||
if (url.endsWith('apk')) {
|
||||
const apkFilePath = path.join(outputDir, `${uuidv4()}.apk`);
|
||||
await downloadFileWithProgressTrackerAsync(
|
||||
url,
|
||||
apkFilePath,
|
||||
(ratio, total) => `Downloading app (${formatBytes(total * ratio)} / ${formatBytes(total)})`,
|
||||
'Successfully downloaded app'
|
||||
);
|
||||
return maybeCacheAppAsync(apkFilePath, cachedAppPath);
|
||||
} else {
|
||||
const tmpArchivePathDir = path.join(getTmpDirectory(), uuidv4());
|
||||
await fs.mkdir(tmpArchivePathDir, { recursive: true });
|
||||
|
||||
const tmpArchivePath = path.join(tmpArchivePathDir, `${uuidv4()}.tar.gz`);
|
||||
const tmpArchivePath = path.join(tmpArchivePathDir, `${uuidv4()}.tar.gz`);
|
||||
|
||||
await downloadFileWithProgressBarAsync(url, tmpArchivePath, 'Downloading app archive...');
|
||||
await tarExtractAsync(tmpArchivePath, outputDir);
|
||||
await downloadFileWithProgressTrackerAsync(
|
||||
url,
|
||||
tmpArchivePath,
|
||||
(ratio, total) =>
|
||||
`Downloading app archive (${formatBytes(total * ratio)} / ${formatBytes(total)})`,
|
||||
'Successfully downloaded app archive'
|
||||
);
|
||||
await tarExtractAsync(tmpArchivePath, outputDir);
|
||||
|
||||
return await getAppPathAsync(outputDir, applicationExtension);
|
||||
const appPath = await getAppPathAsync(outputDir, platform === AppPlatform.Ios ? 'app' : 'apk');
|
||||
|
||||
return maybeCacheAppAsync(appPath, cachedAppPath);
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractAppFromLocalArchiveAsync(
|
||||
appArchivePath: string,
|
||||
applicationExtension: string
|
||||
platform: AppPlatform
|
||||
): Promise<string> {
|
||||
const outputDir = path.join(getTmpDirectory(), uuidv4());
|
||||
await fs.promises.mkdir(outputDir, { recursive: true });
|
||||
|
||||
await tarExtractAsync(appArchivePath, outputDir);
|
||||
|
||||
return await getAppPathAsync(outputDir, applicationExtension);
|
||||
return await getAppPathAsync(outputDir, platform === AppPlatform.Android ? 'apk' : 'app');
|
||||
}
|
||||
|
||||
async function getAppPathAsync(outputDir: string, applicationExtension: string): Promise<string> {
|
||||
|
||||
@@ -23,7 +23,10 @@ function dotExpoHomeDirectory(): string {
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
const getStateJsonPath = (): string => path.join(dotExpoHomeDirectory(), 'state.json');
|
||||
export const getStateJsonPath = (): string => path.join(dotExpoHomeDirectory(), 'state.json');
|
||||
|
||||
export const getEasBuildRunCacheDirectoryPath = (): string =>
|
||||
path.join(getTmpDirectory(), 'eas-build-run-cache');
|
||||
|
||||
// Paths for storing things like data, config, cache, etc.
|
||||
// Should use the correct OS-specific paths (e.g. XDG base directory on Linux)
|
||||
@@ -35,17 +38,8 @@ const {
|
||||
temp: TEMP_PATH,
|
||||
} = envPaths('eas-cli');
|
||||
|
||||
const getDataDirectory = (): string => DATA_PATH;
|
||||
const getConfigDirectory = (): string => CONFIG_PATH;
|
||||
const getCacheDirectory = (): string => CACHE_PATH;
|
||||
const getLogDirectory = (): string => LOG_PATH;
|
||||
const getTmpDirectory = (): string => TEMP_PATH;
|
||||
|
||||
export {
|
||||
getDataDirectory,
|
||||
getConfigDirectory,
|
||||
getCacheDirectory,
|
||||
getLogDirectory,
|
||||
getTmpDirectory,
|
||||
getStateJsonPath,
|
||||
};
|
||||
export const getDataDirectory = (): string => DATA_PATH;
|
||||
export const getConfigDirectory = (): string => CONFIG_PATH;
|
||||
export const getCacheDirectory = (): string => CACHE_PATH;
|
||||
export const getLogDirectory = (): string => LOG_PATH;
|
||||
export const getTmpDirectory = (): string => TEMP_PATH;
|
||||
@@ -21,7 +21,7 @@ export function createProgressTracker({
|
||||
completedMessage,
|
||||
}: {
|
||||
total?: number;
|
||||
message: string | ((ratio: number) => string);
|
||||
message: string | ((ratio: number, total: number) => string);
|
||||
completedMessage: string | ((duration: string) => string);
|
||||
}): ProgressHandler {
|
||||
let bar: Ora | null = null;
|
||||
@@ -31,17 +31,19 @@ export function createProgressTracker({
|
||||
|
||||
const timerLabel = String(Date.now());
|
||||
|
||||
const getMessage = (v: number): string => {
|
||||
const getMessage = (v: number, total: number): string => {
|
||||
const ratio = Math.min(Math.max(v, 0), 1);
|
||||
const percent = Math.floor(ratio * 100);
|
||||
return typeof message === 'string' ? `${message} ${percent.toFixed(0)}%` : message(ratio);
|
||||
return typeof message === 'string'
|
||||
? `${message} ${percent.toFixed(0)}%`
|
||||
: message(ratio, total);
|
||||
};
|
||||
|
||||
return ({ progress, isComplete, error }) => {
|
||||
if (progress) {
|
||||
if (!bar && (progress.total !== undefined || total !== undefined)) {
|
||||
calcTotal = (total ?? progress.total) as number;
|
||||
bar = ora(getMessage(0)).start();
|
||||
bar = ora(getMessage(0, calcTotal)).start();
|
||||
startTimer(timerLabel);
|
||||
}
|
||||
if (progress.total) {
|
||||
@@ -56,7 +58,7 @@ export function createProgressTracker({
|
||||
percentage = current / calcTotal;
|
||||
}
|
||||
|
||||
bar.text = getMessage(percentage);
|
||||
bar.text = getMessage(percentage, calcTotal);
|
||||
}
|
||||
transferredSoFar = progress.transferred;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ import uniqBy from './expodash/uniqBy';
|
||||
|
||||
const fetchMoreValue = '_fetchMore';
|
||||
|
||||
export interface SelectPromptEntry {
|
||||
title: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
type BasePaginatedQueryArgs<QueryReturnType extends Record<string, any>> = {
|
||||
limit: number;
|
||||
offset: number;
|
||||
@@ -21,8 +27,9 @@ type PaginatedQueryWithSelectPromptArgs<QueryReturnType extends Record<string, a
|
||||
BasePaginatedQueryArgs<QueryReturnType> & {
|
||||
promptOptions: {
|
||||
readonly title: string;
|
||||
createDisplayTextForSelectionPromptListItem: (queryItem: QueryReturnType) => string;
|
||||
makePartialChoiceObject: (queryItem: QueryReturnType) => SelectPromptEntry;
|
||||
getIdentifierForQueryItem: (queryItem: QueryReturnType) => string;
|
||||
readonly selectPromptWarningMessage?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -100,7 +107,7 @@ async function paginatedQueryWithSelectPromptInternalAsync<
|
||||
const selectionPromptListItems = uniqBy(newAccumulator, queryItem =>
|
||||
promptOptions.getIdentifierForQueryItem(queryItem)
|
||||
).map(queryItem => ({
|
||||
title: promptOptions.createDisplayTextForSelectionPromptListItem(queryItem),
|
||||
...promptOptions.makePartialChoiceObject(queryItem),
|
||||
value: promptOptions.getIdentifierForQueryItem(queryItem),
|
||||
}));
|
||||
if (areMorePagesAvailable) {
|
||||
@@ -112,7 +119,10 @@ async function paginatedQueryWithSelectPromptInternalAsync<
|
||||
|
||||
const valueOfUserSelectedListItem = await selectAsync<string>(
|
||||
promptOptions.title,
|
||||
selectionPromptListItems
|
||||
selectionPromptListItems,
|
||||
{
|
||||
warningMessageForDisabledEntries: promptOptions.selectPromptWarningMessage,
|
||||
}
|
||||
);
|
||||
|
||||
if (valueOfUserSelectedListItem === fetchMoreValue) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "2.7.1",
|
||||
"version": "3.0.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
@@ -14,6 +14,7 @@
|
||||
"joi": "17.6.4",
|
||||
"log-symbols": "4.1.0",
|
||||
"semver": "7.3.8",
|
||||
"terminal-link": "2.1.1",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,12 +3,40 @@ import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import * as fleece from 'golden-fleece';
|
||||
import { ValidationError } from 'joi';
|
||||
import path from 'path';
|
||||
|
||||
import { InvalidEasJsonError, MissingEasJsonError } from './errors';
|
||||
import { link } from './log';
|
||||
import { EasJsonSchema } from './schema';
|
||||
import { EasJson } from './types';
|
||||
|
||||
const customErrorMessageHandlers: ((err: ValidationError) => void)[] = [
|
||||
// Ask user to upgrade eas-cli version or check the docs when image is invalid.
|
||||
(err: ValidationError) => {
|
||||
for (const detail of err.details) {
|
||||
// image should be only placed under 'build.profilename.platform.image' key
|
||||
// if it's not the case show standard Joi error
|
||||
if (
|
||||
detail.path.length === 4 &&
|
||||
detail.path[0] === 'build' &&
|
||||
['ios', 'android'].includes(detail.path[2].toString()) &&
|
||||
detail.path[3] === 'image'
|
||||
) {
|
||||
throw new InvalidEasJsonError(
|
||||
chalk.red(
|
||||
`Specified build image '${
|
||||
detail?.context?.value
|
||||
}' is not recognized. Please update your EAS CLI and see ${link(
|
||||
'https://docs.expo.dev/build-reference/infrastructure/'
|
||||
)} for supported build images.`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
export class EasJsonAccessor {
|
||||
private easJsonPath: string;
|
||||
|
||||
@@ -39,6 +67,10 @@ export class EasJsonAccessor {
|
||||
noDefaults: true,
|
||||
});
|
||||
if (error) {
|
||||
for (const handler of customErrorMessageHandlers) {
|
||||
handler(error);
|
||||
}
|
||||
|
||||
const errorMessages = error.message.split('. ');
|
||||
throw new InvalidEasJsonError(
|
||||
`${chalk.bold('eas.json')} is not valid.\n- ${errorMessages.join('\n- ')}`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import chalk from 'chalk';
|
||||
import terminalLink from 'terminal-link';
|
||||
|
||||
// link function from packages/eas-cli/src/log.ts
|
||||
export function link(
|
||||
url: string,
|
||||
{ text = url, dim = true }: { text?: string; dim?: boolean } = {}
|
||||
): string {
|
||||
let output: string;
|
||||
if (terminalLink.isSupported) {
|
||||
output = terminalLink(text, url);
|
||||
} else {
|
||||
output = `${text === url ? '' : text + ': '}${chalk.underline(url)}`;
|
||||
}
|
||||
return dim ? chalk.dim(output) : output;
|
||||
}
|
||||
Reference in new issue
Block a user