mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e4663ac44 | ||
|
|
ca18a555c1 | ||
|
|
c446bbb016 | ||
|
|
d5254434c6 | ||
|
|
e2277b9482 | ||
|
|
739502aef9 | ||
|
|
de42624992 | ||
|
|
0ccfbe5dd6 | ||
|
|
3ffad12681 | ||
|
|
565867b5a8 | ||
|
|
303cc82a06 | ||
|
|
4c2abc0693 | ||
|
|
b63167fbae | ||
|
|
8adb6434bf | ||
|
|
4c9e407fef | ||
|
|
ef566b3f9c | ||
|
|
895231ea98 | ||
|
|
c7000a754e | ||
|
|
9ac8404681 | ||
|
|
6c0b7d5951 |
No files matched your search
+33
-3
@@ -6,16 +6,46 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- [eas-cli] change eas update:publish to eas update. ([#830](https://github.com/expo/eas-cli/pull/830) by [@jkhales](https://github.com/jkhales))
|
||||
### 🎉 New features
|
||||
|
||||
- Adds commands for EAS Update, which is now in preview for subscribers. EAS Update makes fixing small bugs and pushing quick fixes a snap in between app store submissions. It accomplishes this by allowing an end-user's app to swap out the non-native parts of their app (for example, JS, styling, and image changes) with a new update that contains bug fixes and other updates.
|
||||
- Adds `eas update`, which can bundle and publish updates on a branch.
|
||||
- Adds `eas branch`, which manages branches. Branches contain a list of updates and are linked to channels.
|
||||
- Adds `eas channel`, which manages channels. Channels are specified inside builds and are linked to branches, allowing us to link specific updates with specific builds.
|
||||
- Read more in our [feature preview docs](https://docs.expo.dev/eas-update/introduction/).
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `eas submit` displaying a prompt in non-interactive mode when some ASC API credentials are missing in `eas.json`. ([#841](https://github.com/expo/eas-cli/pull/841) by [@barthap](https://github.com/barthap))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.41.0](https://github.com/expo/eas-cli/releases/tag/v0.41.0) - 2021-12-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Ask user to select profile if `release` does not exist. ([#829](https://github.com/expo/eas-cli/pull/829) by [@dsokal](https://github.com/dsokal))
|
||||
- Add `build:inspect` command. ([#834](https://github.com/expo/eas-cli/pull/834) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix validating tool versions in eas.json. ([#832](https://github.com/expo/eas-cli/pull/832) by [@dsokal](https://github.com/dsokal))
|
||||
- Fix submission URL. ([#845](https://github.com/expo/eas-cli/pull/845) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Warn when building managed project with SDK < 41. ([#833](https://github.com/expo/eas-cli/pull/833) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.40.0](https://github.com/expo/eas-cli/releases/tag/v0.40.0) - 2021-12-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- [eas-cli] change eas update:publish to eas update. ([#830](https://github.com/expo/eas-cli/pull/830) by [@jkhales](https://github.com/jkhales))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass build profile environment to credentials command. ([#828](https://github.com/expo/eas-cli/pull/828) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.39.0](https://github.com/expo/eas-cli/releases/tag/v0.39.0) - 2021-12-06
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.40.0"
|
||||
"version": "0.41.1"
|
||||
}
|
||||
+286
-26
@@ -37,11 +37,21 @@ eas --help COMMAND
|
||||
* [`eas account:logout`](#eas-accountlogout)
|
||||
* [`eas account:view`](#eas-accountview)
|
||||
* [`eas analytics [STATUS]`](#eas-analytics-status)
|
||||
* [`eas branch:create [NAME]`](#eas-branchcreate-name)
|
||||
* [`eas branch:delete [NAME]`](#eas-branchdelete-name)
|
||||
* [`eas branch:list`](#eas-branchlist)
|
||||
* [`eas branch:rename`](#eas-branchrename)
|
||||
* [`eas branch:view [NAME]`](#eas-branchview-name)
|
||||
* [`eas build`](#eas-build)
|
||||
* [`eas build:cancel [BUILD_ID]`](#eas-buildcancel-build_id)
|
||||
* [`eas build:configure`](#eas-buildconfigure)
|
||||
* [`eas build:inspect`](#eas-buildinspect)
|
||||
* [`eas build:list`](#eas-buildlist)
|
||||
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
|
||||
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
|
||||
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
|
||||
* [`eas channel:list`](#eas-channellist)
|
||||
* [`eas channel:view [NAME]`](#eas-channelview-name)
|
||||
* [`eas config`](#eas-config)
|
||||
* [`eas credentials`](#eas-credentials)
|
||||
* [`eas device:create`](#eas-devicecreate)
|
||||
@@ -55,6 +65,10 @@ eas --help COMMAND
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
* [`eas submit`](#eas-submit)
|
||||
* [`eas update`](#eas-update)
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
* [`eas update:view GROUPID`](#eas-updateview-groupid)
|
||||
* [`eas webhook:create`](#eas-webhookcreate)
|
||||
* [`eas webhook:delete [ID]`](#eas-webhookdelete-id)
|
||||
* [`eas webhook:list`](#eas-webhooklist)
|
||||
@@ -73,7 +87,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -87,7 +101,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -101,7 +115,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -112,7 +126,88 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas branch:create [NAME]`
|
||||
|
||||
Create a branch on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:create [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to create
|
||||
|
||||
OPTIONS
|
||||
--json return a json with the new branch ID and name.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
Delete a branch on the current project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:delete [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to delete
|
||||
|
||||
OPTIONS
|
||||
--json return JSON with the edited branch's ID and name.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
List all branches on this project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:list
|
||||
|
||||
OPTIONS
|
||||
--json return output as JSON
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
Rename a branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:rename
|
||||
|
||||
OPTIONS
|
||||
--from=from current name of the branch.
|
||||
--json return a json with the edited branch's ID and name.
|
||||
--to=to new name of the branch.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
View a branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas branch:view [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the branch to view
|
||||
|
||||
OPTIONS
|
||||
--json return a json with the branch's ID name and recent update groups.
|
||||
```
|
||||
|
||||
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -146,7 +241,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -157,7 +252,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -171,7 +266,39 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
Inspect the state of the project at specific build stages. Useful for troubleshooting.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:inspect
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios)
|
||||
(required)
|
||||
|
||||
-s, --stage=(archive|pre-build|post-build)
|
||||
(required) Stage of the build you want to inspect.
|
||||
archive - builds the project archive that would be uploaded to EAS when building
|
||||
pre-build - prepares the project to be built with Gradle/Xcode. Does not run the native build.
|
||||
post-build - builds the native project and leaves the output directory for inspection
|
||||
|
||||
--force
|
||||
Delete OUTPUT_DIRECTORY if it already exists.
|
||||
|
||||
--output=OUTPUT_DIRECTORY
|
||||
(required) Output directory.
|
||||
|
||||
--profile=PROFILE_NAME
|
||||
Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.
|
||||
|
||||
--verbose
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -204,7 +331,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -218,7 +345,73 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
Create a channel on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:create [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to create
|
||||
|
||||
OPTIONS
|
||||
--json print output as a JSON object with the new channel ID, name and branch mapping.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
Point a channel at a new branch.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:edit [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to edit
|
||||
|
||||
OPTIONS
|
||||
--branch=branch Name of the branch to point to
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
List all channels on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:list
|
||||
|
||||
OPTIONS
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
View a channel on the current project.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas channel:view [NAME]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the channel to view
|
||||
|
||||
OPTIONS
|
||||
--json print output as a JSON object with the channel ID, name and branch mapping.
|
||||
```
|
||||
|
||||
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -233,7 +426,7 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -244,7 +437,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -255,7 +448,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -269,7 +462,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -280,7 +473,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -291,7 +484,7 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -319,7 +512,7 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -333,7 +526,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -350,7 +543,7 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -367,7 +560,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -378,7 +571,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -412,7 +605,74 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
Publish an update group.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update
|
||||
|
||||
OPTIONS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=branch Branch to publish the update group on
|
||||
--group=group Update group to republish
|
||||
--input-dir=input-dir [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
--message=message A short message describing the update
|
||||
--republish Republish an update group
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
Configure the project to support EAS Update.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
Delete all the updates in an update Group.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:delete GROUPID
|
||||
|
||||
ARGUMENTS
|
||||
GROUPID The ID of an update group to delete.
|
||||
|
||||
OPTIONS
|
||||
--json Return a json with the group ID of the deleted updates.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
Update group details.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:view GROUPID
|
||||
|
||||
ARGUMENTS
|
||||
GROUPID The ID of an update group.
|
||||
|
||||
OPTIONS
|
||||
--json Return a json with the updates belonging to the group.
|
||||
```
|
||||
|
||||
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -431,7 +691,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -445,7 +705,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -459,7 +719,7 @@ OPTIONS
|
||||
--event=(BUILD|SUBMIT) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -479,7 +739,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -493,5 +753,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.40.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.41.1/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+202
@@ -4807,6 +4807,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "reactNativeVersion",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "releaseChannel",
|
||||
"description": "",
|
||||
@@ -12902,6 +12914,18 @@
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "couponCode",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
@@ -12991,6 +13015,34 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "discount",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "InvoiceDiscount",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "totalDiscountedAmount",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "lineItems",
|
||||
"description": "",
|
||||
@@ -13085,6 +13137,132 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InvoiceDiscount",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "InvoiceDiscountType",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "durationInMonths",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"description": "The coupon's discount value, in percentage or in dollar amount",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "InvoiceDiscountType",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "PERCENTAGE",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "AMOUNT",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InvoiceLineItem",
|
||||
@@ -14864,6 +15042,18 @@
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "couponCode",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
@@ -18829,6 +19019,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "reactNativeVersion",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "releaseChannel",
|
||||
"description": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.40.0",
|
||||
"version": "0.41.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -12,7 +12,7 @@
|
||||
"@expo/config": "6.0.11",
|
||||
"@expo/config-plugins": "4.0.11",
|
||||
"@expo/eas-build-job": "0.2.61",
|
||||
"@expo/eas-json": "^0.40.0",
|
||||
"@expo/eas-json": "^0.41.0",
|
||||
"@expo/json-file": "8.2.34",
|
||||
"@expo/pkcs12": "0.0.6",
|
||||
"@expo/plist": "0.0.16",
|
||||
|
||||
@@ -57,3 +57,13 @@ export function getExpoWebsiteBaseUrl(): string {
|
||||
return `https://expo.dev`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEASUpdateURL(projectId: string): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return new URL(projectId, `https://staging-u.expo.dev`).href;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return new URL(`expo-updates/${projectId}`, `http://127.0.0.1:3000`).href;
|
||||
} else {
|
||||
return new URL(projectId, `https://u.expo.dev`).href;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
);
|
||||
}
|
||||
|
||||
const projectArchive = ctx.local
|
||||
const projectArchive = ctx.localBuildOptions.enable
|
||||
? ({
|
||||
type: ArchiveSourceType.PATH,
|
||||
path: (await makeProjectTarballAsync()).path,
|
||||
@@ -94,8 +94,8 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
});
|
||||
|
||||
return async () => {
|
||||
if (ctx.local) {
|
||||
await runLocalBuildAsync(job);
|
||||
if (ctx.localBuildOptions.enable) {
|
||||
await runLocalBuildAsync(job, ctx.localBuildOptions);
|
||||
return undefined;
|
||||
} else {
|
||||
try {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { RequestedPlatform } from '../platform';
|
||||
import { GradleBuildContext } from '../project/android/gradle';
|
||||
import { XcodeBuildContext } from '../project/ios/scheme';
|
||||
import { Actor } from '../user/User';
|
||||
import { LocalBuildOptions } from './local';
|
||||
|
||||
export interface ConfigureContext {
|
||||
user: Actor;
|
||||
@@ -44,7 +45,7 @@ export interface BuildContext<T extends Platform> {
|
||||
clearCache: boolean;
|
||||
credentialsCtx: CredentialsContext;
|
||||
exp: ExpoConfig;
|
||||
local: boolean;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
projectDir: string;
|
||||
|
||||
@@ -15,12 +15,13 @@ import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { createAndroidContextAsync } from './android/build';
|
||||
import { BuildContext, CommonContext } from './context';
|
||||
import { createIosContextAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
|
||||
export async function createBuildContextAsync<T extends Platform>({
|
||||
buildProfileName,
|
||||
buildProfile,
|
||||
clearCache = false,
|
||||
local,
|
||||
localBuildOptions,
|
||||
nonInteractive = false,
|
||||
platform,
|
||||
projectDir,
|
||||
@@ -29,7 +30,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
clearCache: boolean;
|
||||
local: boolean;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
projectDir: string;
|
||||
@@ -74,7 +75,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
clearCache,
|
||||
credentialsCtx,
|
||||
exp,
|
||||
local,
|
||||
localBuildOptions,
|
||||
nonInteractive,
|
||||
platform,
|
||||
projectDir,
|
||||
|
||||
@@ -1,15 +1,54 @@
|
||||
import { Job } from '@expo/eas-build-job';
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import semver from 'semver';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.54';
|
||||
import { ora } from '../ora';
|
||||
|
||||
export async function runLocalBuildAsync(job: Job): Promise<void> {
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.56';
|
||||
|
||||
export interface LocalBuildOptions {
|
||||
enable: boolean;
|
||||
skipCleanup?: boolean;
|
||||
skipNativeBuild?: boolean;
|
||||
artifactsDir?: string;
|
||||
workingdir?: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export async function runLocalBuildAsync(job: Job, options: LocalBuildOptions): Promise<void> {
|
||||
const { command, args } = await getCommandAndArgsAsync(job);
|
||||
await spawnAsync(command, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
let spinner;
|
||||
if (!options.verbose) {
|
||||
spinner = ora().start(options.skipNativeBuild ? 'Preparing project' : 'Building project');
|
||||
}
|
||||
let childProcess: ChildProcess | undefined;
|
||||
const interruptHandler = (): void => {
|
||||
if (childProcess) {
|
||||
childProcess.kill();
|
||||
}
|
||||
};
|
||||
process.on('SIGINT', interruptHandler);
|
||||
try {
|
||||
const spawnPromise = spawnAsync(command, args, {
|
||||
stdio: options.verbose ? 'inherit' : 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
|
||||
...(options.skipCleanup || options.skipNativeBuild
|
||||
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
|
||||
: {}),
|
||||
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
|
||||
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
|
||||
},
|
||||
});
|
||||
childProcess = spawnPromise.child;
|
||||
await spawnPromise;
|
||||
} finally {
|
||||
process.removeListener('SIGINT', interruptHandler);
|
||||
spinner?.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async function getCommandAndArgsAsync(job: Job): Promise<{ command: string; args: string[] }> {
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, SubmitProfile } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { AppPlatform, BuildFragment, BuildStatus, SubmissionFragment } from '../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../graphql/types/AppPlatform';
|
||||
import Log from '../log';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
toPlatforms,
|
||||
} from '../platform';
|
||||
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
|
||||
import { createSubmissionContextAsync } from '../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { ProfileData, getProfilesAsync } from '../utils/profiles';
|
||||
import { getVcsClient } from '../vcs';
|
||||
import { prepareAndroidBuildAsync } from './android/build';
|
||||
import { BuildRequestSender, waitForBuildEndAsync } from './build';
|
||||
import { ensureProjectConfiguredAsync } from './configure';
|
||||
import { BuildContext } from './context';
|
||||
import { createBuildContextAsync } from './createContext';
|
||||
import { prepareIosBuildAsync } from './ios/build';
|
||||
import { LocalBuildOptions } from './local';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from './utils/devClient';
|
||||
import { printBuildResults, printLogsUrls } from './utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync } from './utils/repository';
|
||||
|
||||
let metroConfigValidated = false;
|
||||
let sdkVersionChecked = false;
|
||||
|
||||
export interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
wait: boolean;
|
||||
clearCache: boolean;
|
||||
json: boolean;
|
||||
autoSubmit: boolean;
|
||||
submitProfile?: string;
|
||||
localBuildOptions: LocalBuildOptions;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFlags): Promise<void> {
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir, flags.requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(flags.requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
type: 'build',
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.profile ?? undefined,
|
||||
});
|
||||
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printLogsUrls(startedBuilds.map(startedBuild => startedBuild.build));
|
||||
Log.newLine();
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
if (flags.autoSubmit) {
|
||||
const submitProfiles = await getProfilesAsync({
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.submitProfile,
|
||||
type: 'submit',
|
||||
});
|
||||
for (const startedBuild of startedBuilds) {
|
||||
const submitProfile = nullthrows(
|
||||
submitProfiles.find(
|
||||
({ platform }) => toAppPlatform(platform) === startedBuild.build.platform
|
||||
)
|
||||
).profile;
|
||||
const submission = await prepareAndStartSubmissionAsync({
|
||||
build: startedBuild.build,
|
||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile.profile,
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printSubmissionDetailsUrls(submissions);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
if (!flags.wait) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
|
||||
printBuildResults(builds, flags.json);
|
||||
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
|
||||
);
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
exitWithNonZeroCodeIfSomeBuildsFailed(builds);
|
||||
} else {
|
||||
// the following function also exits with non zero code if any of the submissions failed
|
||||
await waitForSubmissionsToCompleteAsync(submissions);
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
clearCache: flags.clearCache,
|
||||
buildProfile: buildProfile.profile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
platform: buildProfile.platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration: flags.skipProjectConfiguration,
|
||||
localBuildOptions: flags.localBuildOptions,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(buildProfile.platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} build`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (buildCtx.workflow === Workflow.MANAGED) {
|
||||
if (!sdkVersionChecked) {
|
||||
await checkExpoSdkIsSupportedAsync(buildCtx);
|
||||
sdkVersionChecked = true;
|
||||
}
|
||||
if (!metroConfigValidated) {
|
||||
await validateMetroConfigForManagedWorkflowAsync(buildCtx);
|
||||
metroConfigValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
const build = await startBuildAsync(buildCtx);
|
||||
return {
|
||||
build,
|
||||
buildCtx,
|
||||
};
|
||||
}
|
||||
|
||||
async function startBuildAsync(ctx: BuildContext<Platform>): Promise<BuildFragment | undefined> {
|
||||
let sendBuildRequestAsync: BuildRequestSender;
|
||||
if (ctx.platform === Platform.ANDROID) {
|
||||
sendBuildRequestAsync = await prepareAndroidBuildAsync(ctx as BuildContext<Platform.ANDROID>);
|
||||
} else {
|
||||
sendBuildRequestAsync = await prepareIosBuildAsync(ctx as BuildContext<Platform.IOS>);
|
||||
}
|
||||
return await sendBuildRequestAsync();
|
||||
}
|
||||
|
||||
async function prepareAndStartSubmissionAsync({
|
||||
build,
|
||||
buildCtx,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
submitProfile,
|
||||
nonInteractive,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
buildCtx: BuildContext<Platform>;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
submitProfile: SubmitProfile;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const platform = toPlatform(build.platform);
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId: build.project.id,
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[build.platform]} submission`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return await submitAsync(submissionCtx);
|
||||
}
|
||||
|
||||
function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | null)[]): void {
|
||||
const failedBuilds = (maybeBuilds.filter(i => i) as BuildFragment[]).filter(
|
||||
i => i.status === BuildStatus.Errored
|
||||
);
|
||||
if (failedBuilds.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ export async function createUpdateBranchOnAppAsync({
|
||||
}
|
||||
|
||||
export default class BranchCreate extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Create a branch on the current project.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -76,8 +76,7 @@ async function deleteBranchOnAppAsync({
|
||||
}
|
||||
|
||||
export default class BranchDelete extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Republish an update group';
|
||||
static description = 'Delete a branch on the current project';
|
||||
|
||||
static args = [
|
||||
{
|
||||
|
||||
@@ -54,8 +54,6 @@ export async function listBranchesAsync({
|
||||
}
|
||||
|
||||
export default class BranchList extends EasCommand {
|
||||
static hidden = true;
|
||||
|
||||
static description = 'List all branches on this project.';
|
||||
|
||||
static flags = {
|
||||
@@ -76,7 +74,7 @@ export default class BranchList extends EasCommand {
|
||||
Log.log(JSON.stringify(branches, null, 2));
|
||||
} else {
|
||||
const table = new CliTable({
|
||||
head: ['branch', ...UPDATE_COLUMNS],
|
||||
head: ['Branch', ...UPDATE_COLUMNS],
|
||||
wordWrap: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ async function renameUpdateBranchOnAppAsync({
|
||||
}
|
||||
|
||||
export default class BranchRename extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Rename a branch.';
|
||||
|
||||
static flags = {
|
||||
|
||||
@@ -67,7 +67,6 @@ export async function viewUpdateBranchAsync({
|
||||
}
|
||||
|
||||
export default class BranchView extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'View a branch.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -1,49 +1,19 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { BuildProfile, EasJsonReader, SubmitProfile } from '@expo/eas-json';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { flags } from '@oclif/command';
|
||||
import { error } from '@oclif/errors';
|
||||
import chalk from 'chalk';
|
||||
import figures from 'figures';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { prepareAndroidBuildAsync } from '../../build/android/build';
|
||||
import { BuildRequestSender, waitForBuildEndAsync } from '../../build/build';
|
||||
import { ensureProjectConfiguredAsync } from '../../build/configure';
|
||||
import { BuildContext } from '../../build/context';
|
||||
import { createBuildContextAsync } from '../../build/createContext';
|
||||
import { prepareIosBuildAsync } from '../../build/ios/build';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from '../../build/utils/devClient';
|
||||
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
|
||||
import { BuildFlags, runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { ensureRepoIsCleanAsync, reviewAndCommitChangesAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildStatus,
|
||||
SubmissionFragment,
|
||||
} from '../../graphql/generated';
|
||||
import { toAppPlatform, toPlatform } from '../../graphql/types/AppPlatform';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import {
|
||||
RequestedPlatform,
|
||||
appPlatformDisplayNames,
|
||||
appPlatformEmojis,
|
||||
selectRequestedPlatformAsync,
|
||||
toPlatforms,
|
||||
} from '../../platform';
|
||||
import { validateMetroConfigForManagedWorkflowAsync } from '../../project/metroConfig';
|
||||
import { RequestedPlatform, selectRequestedPlatformAsync } from '../../platform';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../submit/context';
|
||||
import {
|
||||
submitAsync,
|
||||
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
|
||||
} from '../../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../../submit/utils/urls';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
import { ProfileData, getProfilesAsync } from '../../utils/profiles';
|
||||
import { getVcsClient, setVcsClient } from '../../vcs';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
@@ -61,19 +31,6 @@ interface RawBuildFlags {
|
||||
'auto-submit-with-profile'?: string;
|
||||
}
|
||||
|
||||
interface BuildFlags {
|
||||
requestedPlatform: RequestedPlatform;
|
||||
skipProjectConfiguration: boolean;
|
||||
profile?: string;
|
||||
nonInteractive: boolean;
|
||||
local: boolean;
|
||||
wait: boolean;
|
||||
clearCache: boolean;
|
||||
json: boolean;
|
||||
autoSubmit: boolean;
|
||||
submitProfile?: string;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
static description = 'Start a build';
|
||||
|
||||
@@ -129,112 +86,17 @@ export default class Build extends EasCommand {
|
||||
}),
|
||||
};
|
||||
|
||||
private metroConfigValidated = false;
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags: rawFlags } = this.parse(Build);
|
||||
if (rawFlags.json) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
const flags = await this.sanitizeFlagsAsync(rawFlags);
|
||||
const { requestedPlatform } = flags;
|
||||
|
||||
const projectDir = await findProjectRootAsync();
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
await getVcsClient().ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(flags.nonInteractive);
|
||||
|
||||
await ensureProjectConfiguredAsync(projectDir, requestedPlatform);
|
||||
|
||||
const platforms = toPlatforms(requestedPlatform);
|
||||
const buildProfiles = await getProfilesAsync({
|
||||
type: 'build',
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.profile ?? undefined,
|
||||
});
|
||||
|
||||
await ensureExpoDevClientInstalledForDevClientBuildsAsync({
|
||||
projectDir,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfiles,
|
||||
});
|
||||
|
||||
const startedBuilds: {
|
||||
build: BuildFragment;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}[] = [];
|
||||
const buildCtxByPlatform: { [p in AppPlatform]?: BuildContext<Platform> } = {};
|
||||
|
||||
for (const buildProfile of buildProfiles) {
|
||||
const { build: maybeBuild, buildCtx } = await this.prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds: platforms.length > 1,
|
||||
buildProfile,
|
||||
});
|
||||
if (maybeBuild) {
|
||||
startedBuilds.push({ build: maybeBuild, buildProfile });
|
||||
}
|
||||
buildCtxByPlatform[toAppPlatform(buildProfile.platform)] = buildCtx;
|
||||
}
|
||||
|
||||
if (flags.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printLogsUrls(startedBuilds.map(startedBuild => startedBuild.build));
|
||||
Log.newLine();
|
||||
|
||||
const submissions: SubmissionFragment[] = [];
|
||||
if (flags.autoSubmit) {
|
||||
const submitProfiles = await getProfilesAsync({
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: flags.submitProfile,
|
||||
type: 'submit',
|
||||
});
|
||||
for (const startedBuild of startedBuilds) {
|
||||
const submitProfile = nullthrows(
|
||||
submitProfiles.find(
|
||||
({ platform }) => toAppPlatform(platform) === startedBuild.build.platform
|
||||
)
|
||||
).profile;
|
||||
const submission = await this.prepareAndStartSubmissionAsync({
|
||||
build: startedBuild.build,
|
||||
buildCtx: nullthrows(buildCtxByPlatform[startedBuild.build.platform]),
|
||||
moreBuilds: startedBuilds.length > 1,
|
||||
projectDir,
|
||||
buildProfile: startedBuild.buildProfile.profile,
|
||||
submitProfile,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
});
|
||||
submissions.push(submission);
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
printSubmissionDetailsUrls(submissions);
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
if (!flags.wait) {
|
||||
return;
|
||||
}
|
||||
|
||||
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
|
||||
printBuildResults(builds, flags.json);
|
||||
|
||||
const haveAllBuildsFailedOrCanceled = builds.every(
|
||||
build => build?.status && [BuildStatus.Errored, BuildStatus.Canceled].includes(build?.status)
|
||||
);
|
||||
if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) {
|
||||
this.exitWithNonZeroCodeIfSomeBuildsFailed(builds);
|
||||
} else {
|
||||
// the following function also exits with non zero code if any of the submissions failed
|
||||
await waitForSubmissionsToCompleteAsync(submissions);
|
||||
}
|
||||
await runBuildAndSubmitAsync(projectDir, flags);
|
||||
}
|
||||
|
||||
private async sanitizeFlagsAsync(flags: RawBuildFlags): Promise<BuildFlags> {
|
||||
@@ -274,7 +136,11 @@ export default class Build extends EasCommand {
|
||||
skipProjectConfiguration: flags['skip-project-configuration'],
|
||||
profile,
|
||||
nonInteractive,
|
||||
local: flags['local'],
|
||||
localBuildOptions: flags['local']
|
||||
? { enable: true, verbose: true }
|
||||
: {
|
||||
enable: false,
|
||||
},
|
||||
wait: flags['wait'],
|
||||
clearCache: flags['clear-cache'],
|
||||
json: flags['json'],
|
||||
@@ -282,111 +148,6 @@ export default class Build extends EasCommand {
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? profile,
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAndStartBuildAsync({
|
||||
projectDir,
|
||||
flags,
|
||||
moreBuilds,
|
||||
buildProfile,
|
||||
}: {
|
||||
projectDir: string;
|
||||
flags: BuildFlags;
|
||||
moreBuilds: boolean;
|
||||
buildProfile: ProfileData<'build'>;
|
||||
}): Promise<{ build: BuildFragment | undefined; buildCtx: BuildContext<Platform> }> {
|
||||
const buildCtx = await createBuildContextAsync({
|
||||
buildProfileName: buildProfile.profileName,
|
||||
clearCache: flags.clearCache,
|
||||
buildProfile: buildProfile.profile,
|
||||
local: flags.local,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
platform: buildProfile.platform,
|
||||
projectDir,
|
||||
skipProjectConfiguration: flags.skipProjectConfiguration,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
const appPlatform = toAppPlatform(buildProfile.platform);
|
||||
Log.log(
|
||||
`${appPlatformEmojis[appPlatform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[appPlatform]} build`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (buildCtx.workflow === Workflow.MANAGED && !this.metroConfigValidated) {
|
||||
await validateMetroConfigForManagedWorkflowAsync(buildCtx);
|
||||
this.metroConfigValidated = true;
|
||||
}
|
||||
|
||||
const build = await this.startBuildAsync(buildCtx);
|
||||
return {
|
||||
build,
|
||||
buildCtx,
|
||||
};
|
||||
}
|
||||
|
||||
private async startBuildAsync(ctx: BuildContext<Platform>): Promise<BuildFragment | undefined> {
|
||||
let sendBuildRequestAsync: BuildRequestSender;
|
||||
if (ctx.platform === Platform.ANDROID) {
|
||||
sendBuildRequestAsync = await prepareAndroidBuildAsync(ctx as BuildContext<Platform.ANDROID>);
|
||||
} else {
|
||||
sendBuildRequestAsync = await prepareIosBuildAsync(ctx as BuildContext<Platform.IOS>);
|
||||
}
|
||||
return await sendBuildRequestAsync();
|
||||
}
|
||||
|
||||
private async prepareAndStartSubmissionAsync({
|
||||
build,
|
||||
buildCtx,
|
||||
moreBuilds,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
submitProfile,
|
||||
nonInteractive,
|
||||
}: {
|
||||
build: BuildFragment;
|
||||
buildCtx: BuildContext<Platform>;
|
||||
moreBuilds: boolean;
|
||||
projectDir: string;
|
||||
buildProfile: BuildProfile;
|
||||
submitProfile: SubmitProfile;
|
||||
nonInteractive: boolean;
|
||||
}): Promise<SubmissionFragment> {
|
||||
const platform = toPlatform(build.platform);
|
||||
const submissionCtx = await createSubmissionContextAsync({
|
||||
platform,
|
||||
projectDir,
|
||||
projectId: build.project.id,
|
||||
profile: submitProfile,
|
||||
archiveFlags: { id: build.id },
|
||||
nonInteractive,
|
||||
env: buildProfile.env,
|
||||
credentialsCtx: buildCtx.credentialsCtx,
|
||||
applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} ${chalk.bold(
|
||||
`${appPlatformDisplayNames[build.platform]} submission`
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return await submitAsync(submissionCtx);
|
||||
}
|
||||
|
||||
private exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds: (BuildFragment | null)[]): void {
|
||||
const failedBuilds = (maybeBuilds.filter(i => i) as BuildFragment[]).filter(
|
||||
i => i.status === BuildStatus.Errored
|
||||
);
|
||||
if (failedBuilds.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeprecatedEasJsonAsync(
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { findProjectRootAsync } from '../../project/projectUtils';
|
||||
import { getTmpDirectory } from '../../utils/paths';
|
||||
import { getVcsClient } from '../../vcs';
|
||||
|
||||
enum InspectStage {
|
||||
ARCHIVE = 'archive',
|
||||
PRE_BUILD = 'pre-build',
|
||||
POST_BUILD = 'post-build',
|
||||
}
|
||||
|
||||
const STAGE_DESCRIPTION = `Stage of the build you want to inspect.
|
||||
archive - builds the project archive that would be uploaded to EAS when building
|
||||
pre-build - prepares the project to be built with Gradle/Xcode. Does not run the native build.
|
||||
post-build - builds the native project and leaves the output directory for inspection`;
|
||||
|
||||
export default class BuildInspect extends EasCommand {
|
||||
static description =
|
||||
'Inspect the state of the project at specific build stages. Useful for troubleshooting.';
|
||||
|
||||
static flags = {
|
||||
platform: flags.enum({
|
||||
char: 'p',
|
||||
options: [RequestedPlatform.Android, RequestedPlatform.Ios],
|
||||
required: true,
|
||||
}),
|
||||
profile: flags.string({
|
||||
description:
|
||||
'Name of the build profile from eas.json. Defaults to "production" if defined in eas.json.',
|
||||
helpValue: 'PROFILE_NAME',
|
||||
}),
|
||||
stage: flags.enum({
|
||||
char: 's',
|
||||
description: STAGE_DESCRIPTION,
|
||||
options: [InspectStage.ARCHIVE, InspectStage.PRE_BUILD, InspectStage.POST_BUILD],
|
||||
required: true,
|
||||
}),
|
||||
output: flags.string({
|
||||
description: 'Output directory.',
|
||||
required: true,
|
||||
helpValue: 'OUTPUT_DIRECTORY',
|
||||
}),
|
||||
force: flags.boolean({
|
||||
description: 'Delete OUTPUT_DIRECTORY if it already exists.',
|
||||
default: false,
|
||||
}),
|
||||
verbose: flags.boolean({
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = this.parse(BuildInspect);
|
||||
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
||||
await this.prepareOutputDirAsync(outputDirectory, flags.force);
|
||||
if (flags.stage === InspectStage.ARCHIVE) {
|
||||
const vcs = getVcsClient();
|
||||
await vcs.ensureRepoExistsAsync();
|
||||
await vcs.makeShallowCopyAsync(outputDirectory);
|
||||
Log.withTick(`Project saved to ${outputDirectory}`);
|
||||
} else {
|
||||
const projectDir = await findProjectRootAsync();
|
||||
const tmpWorkingdir = path.join(getTmpDirectory(), uuidv4());
|
||||
try {
|
||||
await runBuildAndSubmitAsync(projectDir, {
|
||||
skipProjectConfiguration: false,
|
||||
nonInteractive: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
requestedPlatform: flags.platform,
|
||||
profile: flags.profile,
|
||||
localBuildOptions: {
|
||||
enable: true,
|
||||
...(flags.stage === InspectStage.PRE_BUILD ? { skipNativeBuild: true } : {}),
|
||||
...(flags.stage === InspectStage.POST_BUILD ? { skipCleanup: true } : {}),
|
||||
verbose: flags.verbose,
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
});
|
||||
if (!flags.verbose) {
|
||||
Log.log(chalk.green('Build successful'));
|
||||
}
|
||||
} catch (err) {
|
||||
if (!flags.verbose) {
|
||||
Log.error('Build failed');
|
||||
Log.error(`Re-run this command with ${chalk.bold('--verbose')} flag to see the logs`);
|
||||
}
|
||||
} finally {
|
||||
const spinner = ora().start(`Copying project build directory to ${outputDirectory}`);
|
||||
try {
|
||||
const tmpBuildDirectory = path.join(tmpWorkingdir, 'build');
|
||||
if (await fs.pathExists(tmpBuildDirectory)) {
|
||||
await fs.copy(tmpBuildDirectory, outputDirectory);
|
||||
}
|
||||
await fs.remove(tmpWorkingdir);
|
||||
spinner.succeed(`Project build directory saved to ${outputDirectory}`);
|
||||
} catch (err) {
|
||||
spinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareOutputDirAsync(outputDir: string, force: boolean): Promise<void> {
|
||||
if (await fs.pathExists(outputDir)) {
|
||||
if (force) {
|
||||
await fs.remove(outputDir);
|
||||
} else {
|
||||
throw new Error(`Directory ${outputDir} already exists`);
|
||||
}
|
||||
}
|
||||
await fs.mkdirp(outputDir);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,6 @@ export async function createUpdateChannelOnAppAsync({
|
||||
}
|
||||
|
||||
export default class ChannelCreate extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Create a channel on the current project.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -95,7 +95,6 @@ export async function updateChannelBranchMappingAsync({
|
||||
}
|
||||
|
||||
export default class ChannelEdit extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Point a channel at a new branch.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -66,7 +66,6 @@ async function getAllUpdateChannelForAppAsync({
|
||||
}
|
||||
|
||||
export default class ChannelList extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'List all channels on the current project.';
|
||||
|
||||
static flags = {
|
||||
|
||||
@@ -192,7 +192,6 @@ export function logChannelDetails(channel: {
|
||||
}
|
||||
|
||||
export default class ChannelView extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'View a channel on the current project.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class Diagnostics extends EasCommand {
|
||||
'react-navigation',
|
||||
'@expo/webpack-config',
|
||||
'expo-dev-client',
|
||||
'expo-updates',
|
||||
],
|
||||
npmGlobalPackages: ['eas-cli', 'expo-cli'],
|
||||
},
|
||||
|
||||
@@ -2,27 +2,28 @@ import { ExpoConfig, getConfig, modifyConfigAsync } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getEASUpdateURL } from '../../api';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
|
||||
const EAS_UPDATE_URL = 'https://u.expo.dev';
|
||||
const DEFAULT_RUNTIME_VERSION = { policy: 'sdkVersion' } as const;
|
||||
|
||||
export async function getEASUpdateURLAsync(exp: ExpoConfig): Promise<string> {
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
return new URL(projectId, EAS_UPDATE_URL).href;
|
||||
}
|
||||
const DEFAULT_MANAGED_RUNTIME_VERSION = { policy: 'sdkVersion' } as const;
|
||||
const DEFAULT_BARE_RUNTIME_VERSION = '1.0.0';
|
||||
|
||||
async function configureProjectForEASUpdateAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
exp: ExpoConfig,
|
||||
isBare: boolean
|
||||
): Promise<void> {
|
||||
const easUpdateURL = await getEASUpdateURLAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const easUpdateURL = getEASUpdateURL(projectId);
|
||||
const preexistingRuntimeVersion = exp.runtimeVersion;
|
||||
const defaultRuntimeVersion = isBare
|
||||
? DEFAULT_BARE_RUNTIME_VERSION
|
||||
: DEFAULT_MANAGED_RUNTIME_VERSION;
|
||||
const result = await modifyConfigAsync(projectDir, {
|
||||
runtimeVersion: preexistingRuntimeVersion ?? DEFAULT_RUNTIME_VERSION,
|
||||
runtimeVersion: preexistingRuntimeVersion ?? defaultRuntimeVersion,
|
||||
updates: { ...exp.updates, url: easUpdateURL },
|
||||
});
|
||||
|
||||
@@ -39,7 +40,7 @@ async function configureProjectForEASUpdateAsync(
|
||||
}
|
||||
if (!preexistingRuntimeVersion) {
|
||||
Log.withTick(
|
||||
`Set runtimeVersion to "${JSON.stringify(DEFAULT_RUNTIME_VERSION)}" in app.json`
|
||||
`Set runtimeVersion to "${JSON.stringify(defaultRuntimeVersion)}" in app.json`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +73,6 @@ async function configureProjectForEASUpdateAsync(
|
||||
}
|
||||
|
||||
export default class UpdateConfigure extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Configure the project to support EAS Update.';
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
@@ -85,15 +85,16 @@ export default class UpdateConfigure extends EasCommand {
|
||||
skipSDKVersionRequirement: true,
|
||||
});
|
||||
|
||||
await configureProjectForEASUpdateAsync(projectDir, exp);
|
||||
|
||||
const hasAndroidNativeProject =
|
||||
(await resolveWorkflowAsync(projectDir, Platform.ANDROID)) === Workflow.GENERIC;
|
||||
const hasIosNativeProject =
|
||||
(await resolveWorkflowAsync(projectDir, Platform.IOS)) === Workflow.GENERIC;
|
||||
const isBare = hasAndroidNativeProject || hasIosNativeProject;
|
||||
|
||||
await configureProjectForEASUpdateAsync(projectDir, exp, isBare);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
if (hasAndroidNativeProject || hasIosNativeProject) {
|
||||
if (isBare) {
|
||||
Log.log(
|
||||
`🧐 It seems you are on the bare workflow! Please also update your native files. You can do this by either running ${chalk.bold(
|
||||
'eas build:configure'
|
||||
|
||||
@@ -35,7 +35,6 @@ async function deleteUpdateGroupAsync({
|
||||
}
|
||||
|
||||
export default class UpdateDelete extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Delete all the updates in an update Group.';
|
||||
|
||||
static args = [
|
||||
|
||||
@@ -7,6 +7,7 @@ import chalk from 'chalk';
|
||||
import dateFormat from 'dateformat';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { getEASUpdateURL } from '../../api';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import {
|
||||
@@ -39,7 +40,6 @@ import { createUpdateBranchOnAppAsync } from '../branch/create';
|
||||
import { listBranchesAsync } from '../branch/list';
|
||||
import { viewUpdateBranchAsync } from '../branch/view';
|
||||
import { createUpdateChannelOnAppAsync } from '../channel/create';
|
||||
import { getEASUpdateURLAsync } from './configure';
|
||||
|
||||
export const defaultPublishPlatforms: PublishPlatform[] = ['android', 'ios'];
|
||||
type PlatformFlag = PublishPlatform | 'all';
|
||||
@@ -131,7 +131,6 @@ async function ensureBranchExistsAsync({
|
||||
}
|
||||
|
||||
export default class UpdatePublish extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Publish an update group.';
|
||||
|
||||
static flags = {
|
||||
@@ -211,24 +210,36 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
|
||||
if (!branchName) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
const validationMessage = 'Branch name may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
|
||||
const branches = await listBranchesAsync({ projectId });
|
||||
branchName = await selectAsync<string>(
|
||||
'which branch would you like to publish on?',
|
||||
branches.map(branch => {
|
||||
return {
|
||||
title: `${branch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdate(branch.updates[0])}`
|
||||
)}`,
|
||||
value: branch.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
assert(branchName, 'branch name must be specified.');
|
||||
if (branches.length === 0) {
|
||||
({ name: branchName } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
message: 'No branches found. Creating a new one. Please name the new branch:',
|
||||
initial:
|
||||
(await getVcsClient().getBranchNameAsync()) ||
|
||||
`branch-${Math.random().toString(36).substr(2, 4)}`,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
} else {
|
||||
branchName = await selectAsync<string>(
|
||||
'Which branch would you like to publish on?',
|
||||
branches.map(branch => {
|
||||
return {
|
||||
title: `${branch.name} ${chalk.grey(
|
||||
`- current update: ${formatUpdate(branch.updates[0])}`
|
||||
)}`,
|
||||
value: branch.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
assert(branchName, 'Branch name must be specified.');
|
||||
}
|
||||
|
||||
const { id: branchId, updates } = await ensureBranchExistsAsync({
|
||||
@@ -293,7 +304,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
}
|
||||
publicationPlatformMessage = `The republished update will appear on the same plaforms it was originally published on: ${updatesToRepublishFilteredByPlatform
|
||||
.map(update => update.platform)
|
||||
.join(',')}`;
|
||||
.join(', ')}`;
|
||||
} else {
|
||||
publicationPlatformMessage = `The republished update will appear only on: ${platformFlag}`;
|
||||
}
|
||||
@@ -310,6 +321,20 @@ export default class UpdatePublish extends EasCommand {
|
||||
group = updatesToRepublishFilteredByPlatform[0].group;
|
||||
oldMessage = updatesToRepublishFilteredByPlatform[0].message ?? '';
|
||||
oldRuntimeVersion = updatesToRepublishFilteredByPlatform[0].runtimeVersion;
|
||||
|
||||
if (!message) {
|
||||
const validationMessage = 'publish message may not be empty.';
|
||||
if (jsonFlag) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
({ publishMessage: message } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'publishMessage',
|
||||
message: `Please enter an update message.`,
|
||||
initial: `Republish "${oldMessage!}" - group: ${group}`,
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (!message && autoFlag) {
|
||||
message = (await getVcsClient().getLastCommitMessageAsync())?.trim();
|
||||
@@ -324,9 +349,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
type: 'text',
|
||||
name: 'publishMessage',
|
||||
message: `Please enter an update message.`,
|
||||
initial: republish
|
||||
? `Republish "${oldMessage!}" - group: ${group}`
|
||||
: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
initial: (await getVcsClient().getLastCommitMessageAsync())?.trim(),
|
||||
validate: (value: any) => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
@@ -473,7 +496,8 @@ function formatUpdateTitle(
|
||||
|
||||
async function checkEASUpdateURLIsSetAsync(exp: ExpoConfig): Promise<void> {
|
||||
const configuredURL = exp.updates?.url;
|
||||
const expectedURL = await getEASUpdateURLAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const expectedURL = getEASUpdateURL(projectId);
|
||||
|
||||
if (configuredURL !== expectedURL) {
|
||||
throw new Error(
|
||||
|
||||
@@ -50,7 +50,6 @@ export async function viewUpdateAsync({
|
||||
return data;
|
||||
}
|
||||
export default class UpdateView extends EasCommand {
|
||||
static hidden = true;
|
||||
static description = 'Update group details.';
|
||||
|
||||
static args = [
|
||||
|
||||
+36
-14
@@ -720,6 +720,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
appBuildVersion?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
runtimeVersion?: Maybe<Scalars['String']>;
|
||||
reactNativeVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
channel?: Maybe<Scalars['String']>;
|
||||
metrics?: Maybe<BuildMetrics>;
|
||||
@@ -1772,6 +1773,7 @@ export type InvoiceQuery = {
|
||||
export type InvoiceQueryPreviewInvoiceForSubscriptionUpdateArgs = {
|
||||
accountId: Scalars['String'];
|
||||
newPlanIdentifier: Scalars['String'];
|
||||
couponCode?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type Invoice = {
|
||||
@@ -1783,6 +1785,8 @@ export type Invoice = {
|
||||
amountPaid: Scalars['Int'];
|
||||
/** The total amount that needs to be paid, considering any discounts or account credit. Value is in cents. */
|
||||
amountRemaining: Scalars['Int'];
|
||||
discount?: Maybe<InvoiceDiscount>;
|
||||
totalDiscountedAmount: Scalars['Int'];
|
||||
lineItems: Array<InvoiceLineItem>;
|
||||
period: InvoicePeriod;
|
||||
startingBalance: Scalars['Int'];
|
||||
@@ -1790,6 +1794,22 @@ export type Invoice = {
|
||||
total: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type InvoiceDiscount = {
|
||||
__typename?: 'InvoiceDiscount';
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
type: InvoiceDiscountType;
|
||||
duration: Scalars['String'];
|
||||
durationInMonths?: Maybe<Scalars['Int']>;
|
||||
/** The coupon's discount value, in percentage or in dollar amount */
|
||||
amount: Scalars['Int'];
|
||||
};
|
||||
|
||||
export enum InvoiceDiscountType {
|
||||
Percentage = 'PERCENTAGE',
|
||||
Amount = 'AMOUNT'
|
||||
}
|
||||
|
||||
export type InvoiceLineItem = {
|
||||
__typename?: 'InvoiceLineItem';
|
||||
id: Scalars['ID'];
|
||||
@@ -2115,6 +2135,7 @@ export type AccountMutationCancelSubscriptionArgs = {
|
||||
export type AccountMutationChangePlanArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
newPlanIdentifier: Scalars['String'];
|
||||
couponCode?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2744,6 +2765,7 @@ export type BuildMetadataInput = {
|
||||
credentialsSource?: Maybe<BuildCredentialsSource>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
runtimeVersion?: Maybe<Scalars['String']>;
|
||||
reactNativeVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
channel?: Maybe<Scalars['String']>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
@@ -3681,19 +3703,6 @@ export type BranchesByAppQuery = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetUpdateGroupAsyncQueryVariables = Exact<{
|
||||
group: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetUpdateGroupAsyncQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { updatesByGroup: Array<(
|
||||
{ __typename?: 'Update' }
|
||||
& Pick<Update, 'id' | 'group' | 'runtimeVersion' | 'manifestFragment' | 'platform' | 'message'>
|
||||
)> }
|
||||
);
|
||||
|
||||
export type EditUpdateBranchMutationVariables = Exact<{
|
||||
input: EditUpdateBranchInput;
|
||||
}>;
|
||||
@@ -3924,6 +3933,19 @@ export type DeleteUpdateGroupMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetUpdateGroupAsyncQueryVariables = Exact<{
|
||||
group: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetUpdateGroupAsyncQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { updatesByGroup: Array<(
|
||||
{ __typename?: 'Update' }
|
||||
& Pick<Update, 'id' | 'group' | 'runtimeVersion' | 'manifestFragment' | 'platform' | 'message'>
|
||||
)> }
|
||||
);
|
||||
|
||||
export type UpdatesByGroupQueryVariables = Exact<{
|
||||
groupId: Scalars['ID'];
|
||||
}>;
|
||||
@@ -5449,7 +5471,7 @@ export type SubmissionFragment = (
|
||||
& Pick<Submission, 'id' | 'status' | 'platform' | 'logsUrl'>
|
||||
& { app: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id' | 'name'>
|
||||
& Pick<App, 'id' | 'name' | 'slug'>
|
||||
& { ownerAccount: (
|
||||
{ __typename?: 'Account' }
|
||||
& Pick<Account, 'id' | 'name'>
|
||||
|
||||
@@ -8,6 +8,7 @@ export const SubmissionFragmentNode = gql`
|
||||
app {
|
||||
id
|
||||
name
|
||||
slug
|
||||
ownerAccount {
|
||||
id
|
||||
name
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { exit } from '@oclif/errors';
|
||||
import assert from 'assert';
|
||||
import semver from 'semver';
|
||||
|
||||
import { BuildContext } from '../build/context';
|
||||
import Log from '../log';
|
||||
import { confirmAsync } from '../prompts';
|
||||
|
||||
const SUPPORTED_EXPO_SDK_VERSIONS = '>= 41.0.0';
|
||||
assert(semver.validRange(SUPPORTED_EXPO_SDK_VERSIONS), 'Must be a valid version range');
|
||||
|
||||
export async function checkExpoSdkIsSupportedAsync(ctx: BuildContext<Platform>): Promise<void> {
|
||||
assert(ctx.workflow === Workflow.MANAGED, 'Must be a managed workflow project');
|
||||
|
||||
if (ctx.exp.sdkVersion && semver.satisfies(ctx.exp.sdkVersion, SUPPORTED_EXPO_SDK_VERSIONS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsupportedSdkMessage =
|
||||
'EAS Build does not officially support building managed project with Expo SDK < 41.';
|
||||
if (ctx.nonInteractive) {
|
||||
Log.warn(
|
||||
`${unsupportedSdkMessage} Proceeding because you are running in non-interactive mode.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const proceed = await confirmAsync({
|
||||
message: `${unsupportedSdkMessage} Do you want to proceed?`,
|
||||
});
|
||||
if (!proceed) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@ import prompts, { Answers, Choice, Options, PromptType, PromptObject as Question
|
||||
|
||||
export { PromptType, Question, Choice };
|
||||
|
||||
export interface ExpoChoice<T> extends Choice {
|
||||
value: T;
|
||||
}
|
||||
|
||||
type NamelessQuestion = Omit<Question<'value'>, 'name' | 'type'>;
|
||||
|
||||
export async function promptAsync<T extends string = string>(
|
||||
@@ -35,11 +39,6 @@ export async function confirmAsync(
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
interface ExpoChoice<T> extends Choice {
|
||||
value: T;
|
||||
}
|
||||
|
||||
export async function selectAsync<T>(
|
||||
message: string,
|
||||
choices: ExpoChoice<T>[],
|
||||
|
||||
@@ -174,7 +174,14 @@ export default class IosSubmitCommand {
|
||||
|
||||
// interpret this to mean the user had some intention of passing in ASC Api key
|
||||
if (ascApiKeyPath || ascApiKeyIssuerId || ascApiKeyId) {
|
||||
Log.warn(`ascApiKeyPath, ascApiKeyIssuerId and ascApiKeyId must all be defined in eas.json`);
|
||||
const message = `ascApiKeyPath, ascApiKeyIssuerId and ascApiKeyId must all be defined in eas.json`;
|
||||
|
||||
// in non-interactive mode, we should fail
|
||||
if (this.ctx.nonInteractive) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
Log.warn(message);
|
||||
return result({
|
||||
sourceType: AscApiKeySourceType.prompt,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ export function printSubmissionDetailsUrls(submissions: SubmissionFragment[]): v
|
||||
export function getSubmissionDetailsUrl(submission: SubmissionFragment): string {
|
||||
const { id, app } = submission;
|
||||
return new URL(
|
||||
`/accounts/${app.ownerAccount.name}/projects/${app.name}/submissions/${id}`,
|
||||
`/accounts/${app.ownerAccount.name}/projects/${app.slug}/submissions/${id}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
@@ -9,10 +9,10 @@ export type FormatUpdateParameter = Pick<Update, 'id' | 'createdAt' | 'message'>
|
||||
};
|
||||
|
||||
export const UPDATE_COLUMNS = [
|
||||
'update description',
|
||||
'update runtime version',
|
||||
'update group ID',
|
||||
'platforms',
|
||||
'Update message',
|
||||
'Update runtime version',
|
||||
'Update group ID',
|
||||
'Update platforms',
|
||||
];
|
||||
|
||||
export function getPlatformsForGroup({
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { EasJsonReader, errors } from '@expo/eas-json';
|
||||
import { BuildProfile, EasJsonReader, errors } from '@expo/eas-json';
|
||||
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { getProfilesAsync } from '../profiles';
|
||||
|
||||
jest.mock('../../prompts');
|
||||
jest.mock('@expo/eas-json', () => {
|
||||
const actual = jest.requireActual('@expo/eas-json');
|
||||
|
||||
const EasJsonReaderMock = jest.fn();
|
||||
EasJsonReaderMock.prototype = {
|
||||
getBuildProfileAsync: jest.fn(),
|
||||
getSubmitProfileAsync: jest.fn(),
|
||||
getBuildProfileNamesAsync: jest.fn(),
|
||||
};
|
||||
return {
|
||||
...actual,
|
||||
@@ -18,10 +21,13 @@ jest.mock('@expo/eas-json', () => {
|
||||
});
|
||||
|
||||
const getBuildProfileAsync = jest.spyOn(EasJsonReader.prototype, 'getBuildProfileAsync');
|
||||
const getBuildProfileNamesAsync = jest.spyOn(EasJsonReader.prototype, 'getBuildProfileNamesAsync');
|
||||
|
||||
describe(getProfilesAsync, () => {
|
||||
afterEach(() => {
|
||||
getBuildProfileAsync.mockReset();
|
||||
getBuildProfileNamesAsync.mockReset();
|
||||
asMock(selectAsync).mockReset();
|
||||
});
|
||||
|
||||
it('defaults to production profile', async () => {
|
||||
@@ -62,10 +68,32 @@ describe(getProfilesAsync, () => {
|
||||
expect(getBuildProfileAsync).toBeCalledWith(Platform.IOS, 'release');
|
||||
});
|
||||
|
||||
it('fails when neither production or release profiles are present', async () => {
|
||||
getBuildProfileAsync.mockImplementation(() => {
|
||||
it('asks the user to pick profile if "release" does not exist', async () => {
|
||||
getBuildProfileAsync.mockImplementation(async (_, profileName) => {
|
||||
if (profileName === 'foo') {
|
||||
return {} as unknown as BuildProfile<Platform>;
|
||||
}
|
||||
throw new errors.MissingProfileError();
|
||||
});
|
||||
getBuildProfileNamesAsync.mockImplementation(() => Promise.resolve(['foo', 'bar']));
|
||||
asMock(selectAsync).mockImplementation(() => 'foo');
|
||||
|
||||
const result = await getProfilesAsync({
|
||||
projectDir: '/fake',
|
||||
platforms: [Platform.ANDROID],
|
||||
profileName: undefined,
|
||||
type: 'build',
|
||||
});
|
||||
expect(selectAsync).toHaveBeenCalled();
|
||||
expect(getBuildProfileAsync).toBeCalledWith(Platform.ANDROID, 'foo');
|
||||
expect(result[0].profileName).toBe('foo');
|
||||
});
|
||||
|
||||
it('throws an error if there are no profiles in eas.json', async () => {
|
||||
getBuildProfileAsync.mockImplementation(async () => {
|
||||
throw new errors.MissingProfileError();
|
||||
});
|
||||
getBuildProfileNamesAsync.mockImplementation(() => Promise.resolve([]));
|
||||
|
||||
await expect(
|
||||
getProfilesAsync({
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
errors,
|
||||
getDefaultSubmitProfile,
|
||||
} from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../log';
|
||||
import { ExpoChoice, selectAsync } from '../prompts';
|
||||
|
||||
type EasProfile<T extends ProfileType> = T extends 'build'
|
||||
? BuildProfile<Platform>
|
||||
@@ -23,7 +25,7 @@ export type ProfileData<T extends ProfileType> = {
|
||||
export async function getProfilesAsync<T extends ProfileType>({
|
||||
projectDir,
|
||||
platforms,
|
||||
profileName: profileNameArg,
|
||||
profileName,
|
||||
type,
|
||||
}: {
|
||||
projectDir: string;
|
||||
@@ -32,42 +34,98 @@ export async function getProfilesAsync<T extends ProfileType>({
|
||||
type: T;
|
||||
}): Promise<ProfileData<T>[]> {
|
||||
const results = platforms.map(async function (platform) {
|
||||
let profile: EasProfile<T>;
|
||||
let profileName = profileNameArg;
|
||||
|
||||
if (!profileName) {
|
||||
try {
|
||||
profileName = 'production';
|
||||
profile = await readProfileAsync({ projectDir, platform, type, profileName });
|
||||
} catch (errorOuter) {
|
||||
if (!(errorOuter instanceof errors.MissingProfileError)) {
|
||||
throw errorOuter;
|
||||
}
|
||||
try {
|
||||
profileName = 'release';
|
||||
profile = await readProfileAsync({ projectDir, platform, type, profileName });
|
||||
Log.warn(
|
||||
'The default profile changed from "release" to "production". We detected that you still have a "release" build profile, so we are using it. Update eas.json to have a profile named "production" under the `build` key, or specify which profile you\'d like to use with the --profile flag. This fallback behavior will be removed in the next major version of EAS CLI.'
|
||||
);
|
||||
} catch (errorInner) {
|
||||
if (!(errorInner instanceof errors.MissingProfileError)) {
|
||||
throw errorInner;
|
||||
}
|
||||
const defaultProfile = getDefaultProfile({ platform, type });
|
||||
if (!defaultProfile) {
|
||||
throw errorInner;
|
||||
}
|
||||
profileName = '__default__';
|
||||
profile = defaultProfile;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
profile = await readProfileAsync({ projectDir, platform, type, profileName });
|
||||
if (profileName) {
|
||||
const profile = await readProfileAsync({ projectDir, platform, type, profileName });
|
||||
return {
|
||||
profile,
|
||||
profileName,
|
||||
platform,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const profile = await readProfileAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
type,
|
||||
profileName: 'production',
|
||||
});
|
||||
return {
|
||||
profile,
|
||||
profileName: 'production',
|
||||
platform,
|
||||
};
|
||||
} catch (error) {
|
||||
if (!(error instanceof errors.MissingProfileError)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const profile = await readProfileAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
type,
|
||||
profileName: 'release',
|
||||
});
|
||||
Log.warn(
|
||||
`The default profile changed from ${chalk.bold('release')} to ${chalk.bold(
|
||||
'production'
|
||||
)}. We detected that you still have a ${chalk.bold(
|
||||
'release'
|
||||
)} build profile, so we are using it. Update eas.json to have a profile named ${chalk.bold(
|
||||
'production'
|
||||
)} under the ${chalk.bold(
|
||||
'build'
|
||||
)} key, or specify which profile you'd like to use with the ${chalk.bold(
|
||||
'--profile'
|
||||
)} flag. This fallback behavior will be removed in the next major version of EAS CLI.`
|
||||
);
|
||||
return {
|
||||
profile,
|
||||
profileName: 'release',
|
||||
platform,
|
||||
};
|
||||
} catch (error) {
|
||||
if (!(error instanceof errors.MissingProfileError)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const defaultProfile = getDefaultProfile({ platform, type });
|
||||
if (defaultProfile) {
|
||||
return {
|
||||
profile: defaultProfile,
|
||||
profileName: '__default__',
|
||||
platform,
|
||||
};
|
||||
}
|
||||
|
||||
const profileNames = await readProfileNamesAsync({
|
||||
projectDir,
|
||||
type,
|
||||
});
|
||||
if (profileNames.length === 0) {
|
||||
throw new errors.MissingProfileError(
|
||||
`Missing profile in eas.json: ${profileName ?? 'production'}`
|
||||
);
|
||||
}
|
||||
const choices: ExpoChoice<string>[] = profileNames.map(profileName => ({
|
||||
title: profileName,
|
||||
value: profileName,
|
||||
}));
|
||||
const chosenProfileName = await selectAsync(
|
||||
'The "production" profile is missing in eas.json. Pick another profile:',
|
||||
choices
|
||||
);
|
||||
const profile = await readProfileAsync({
|
||||
projectDir,
|
||||
platform,
|
||||
type,
|
||||
profileName: chosenProfileName,
|
||||
});
|
||||
return {
|
||||
profile,
|
||||
profileName,
|
||||
profileName: chosenProfileName,
|
||||
platform,
|
||||
};
|
||||
});
|
||||
@@ -107,3 +165,18 @@ function getDefaultProfile<T extends ProfileType>({
|
||||
return getDefaultSubmitProfile(platform) as EasProfile<T>;
|
||||
}
|
||||
}
|
||||
|
||||
async function readProfileNamesAsync({
|
||||
projectDir,
|
||||
type,
|
||||
}: {
|
||||
projectDir: string;
|
||||
type: ProfileType;
|
||||
}): Promise<string[]> {
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
if (type === 'build') {
|
||||
return await easJsonReader.getBuildProfileNamesAsync();
|
||||
} else {
|
||||
return await easJsonReader.getSubmitProfileNamesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "0.40.0",
|
||||
"version": "0.41.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
@@ -12,6 +12,7 @@
|
||||
"fs-extra": "10.0.0",
|
||||
"joi": "17.4.2",
|
||||
"log-symbols": "4.1.0",
|
||||
"semver": "7.3.5",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -253,14 +253,14 @@ test('empty json', async () => {
|
||||
test('invalid semver value', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: { node: '12.0.0-alpha' },
|
||||
production: { node: 'alpha' },
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const promise = reader.getBuildProfileAsync(Platform.ANDROID, 'production');
|
||||
await expect(promise).rejects.toThrowError(
|
||||
'eas.json is not valid [ValidationError: "build.production.node" failed custom validation because 12.0.0-alpha is not a valid version]'
|
||||
'eas.json is not valid [ValidationError: "build.production.node" failed custom validation because alpha is not a valid version]'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -279,7 +279,7 @@ test('invalid release channel', async () => {
|
||||
test('get profile names', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
build: {
|
||||
production: { node: '12.0.0' },
|
||||
production: { node: '12.0.0-alpha' },
|
||||
blah: { node: '12.0.0' },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -195,3 +195,28 @@ test('valid profile extending other profile', async () => {
|
||||
ascApiKeyId: 'ABCD',
|
||||
});
|
||||
});
|
||||
|
||||
test('get profile names', async () => {
|
||||
await fs.writeJson('/project/eas.json', {
|
||||
submit: {
|
||||
production: {
|
||||
android: {
|
||||
serviceAccountKeyPath: './path.json',
|
||||
track: 'beta',
|
||||
releaseStatus: 'completed',
|
||||
},
|
||||
},
|
||||
blah: {
|
||||
android: {
|
||||
serviceAccountKeyPath: './path.json',
|
||||
track: 'internal',
|
||||
releaseStatus: 'completed',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const reader = new EasJsonReader('/project');
|
||||
const allProfileNames = await reader.getSubmitProfileNamesAsync();
|
||||
expect(allProfileNames.sort()).toEqual(['production', 'blah'].sort());
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Android, Ios } from '@expo/eas-build-job';
|
||||
import Joi from 'joi';
|
||||
import semver from 'semver';
|
||||
|
||||
const CacheSchema = Joi.object({
|
||||
disabled: Joi.boolean(),
|
||||
@@ -16,9 +17,9 @@ const CommonBuildProfileSchema = Joi.object({
|
||||
channel: Joi.string().regex(/^[a-z\d][a-z\d._-]*$/),
|
||||
developmentClient: Joi.boolean(),
|
||||
|
||||
node: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
yarn: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
expoCli: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
node: Joi.string().empty(null).custom(semverCheck),
|
||||
yarn: Joi.string().empty(null).custom(semverCheck),
|
||||
expoCli: Joi.string().empty(null).custom(semverCheck),
|
||||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)),
|
||||
});
|
||||
|
||||
@@ -29,7 +30,7 @@ const AndroidBuildProfileSchema = CommonBuildProfileSchema.concat(
|
||||
withoutCredentials: Joi.boolean(),
|
||||
|
||||
image: Joi.string().valid(...Android.builderBaseImages),
|
||||
ndk: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
ndk: Joi.string().empty(null).custom(semverCheck),
|
||||
autoIncrement: Joi.alternatives().try(
|
||||
Joi.boolean(),
|
||||
Joi.string().valid('version', 'versionCode')
|
||||
@@ -54,9 +55,9 @@ const IosBuildProfileSchema = CommonBuildProfileSchema.concat(
|
||||
simulator: Joi.boolean(),
|
||||
|
||||
image: Joi.string().valid(...Ios.builderBaseImages),
|
||||
bundler: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
fastlane: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
cocoapods: Joi.string().empty(null).custom(semverSchemaCheck),
|
||||
bundler: Joi.string().empty(null).custom(semverCheck),
|
||||
fastlane: Joi.string().empty(null).custom(semverCheck),
|
||||
cocoapods: Joi.string().empty(null).custom(semverCheck),
|
||||
|
||||
artifactPath: Joi.string(),
|
||||
scheme: Joi.string(),
|
||||
@@ -72,8 +73,8 @@ export const BuildProfileSchema = CommonBuildProfileSchema.concat(
|
||||
})
|
||||
);
|
||||
|
||||
function semverSchemaCheck(value: any): any {
|
||||
if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) {
|
||||
function semverCheck(value: any): any {
|
||||
if (semver.valid(value)) {
|
||||
return value;
|
||||
} else {
|
||||
throw new Error(`${value} is not a valid version`);
|
||||
|
||||
@@ -77,6 +77,11 @@ export class EasJsonReader {
|
||||
}
|
||||
}
|
||||
|
||||
public async getSubmitProfileNamesAsync(): Promise<string[]> {
|
||||
const easJson = await this.readAsync();
|
||||
return Object.keys(easJson?.submit ?? {});
|
||||
}
|
||||
|
||||
public async getSubmitProfileAsync<T extends Platform>(
|
||||
platform: T,
|
||||
profileName: string
|
||||
|
||||
@@ -48,7 +48,7 @@ function resolveProfile<T extends Platform>({
|
||||
const profile = easJson.submit?.[profileName];
|
||||
if (!profile) {
|
||||
if (depth === 0) {
|
||||
throw new MissingProfileError(`There is no submit profile named ${profileName} in eas.json`);
|
||||
throw new MissingProfileError(`Missing submit profile in eas.json: ${profileName}`);
|
||||
} else {
|
||||
throw new MissingParentProfileError(
|
||||
`Extending non-existent submit profile in eas.json: ${profileName}`
|
||||
|
||||
Reference in new issue
Block a user