Compare commits

...
20 Commits
Author SHA1 Message Date
jonsamp 5e4663ac44 v0.41.1 2021-12-16 12:53:18 -05:00
jkh ca18a555c1 [eas-cli] support staging and local Update URLs (#849) 2021-12-16 07:14:11 -08:00
Jon Samp c446bbb016 [eas-cli] Make all EAS Update related commands public (#854) 2021-12-16 09:34:49 -05:00
Dominik Sokal d5254434c6 print expo-updates version in eas diagnostics 2021-12-16 09:58:40 +01:00
Bartłomiej Klocek e2277b9482 [eas-cli] Fix ASC prompt displayed in --non-interactive (#841)
* [eas-cli] Fix ASC prompt displayed in --non-interactive

* Update CHANGELOG.md

* Apply feedback - fail in non-interactive
2021-12-15 13:28:59 +01:00
Test User 739502aef9 [eas-cli] fix typo in branch:delete description 2021-12-13 17:26:36 -05:00
Expo CI de42624992 update CHANGELOG.md 2021-12-13 15:44:24 +00:00
Wojciech Kozyra 0ccfbe5dd6 v0.41.0 2021-12-13 16:19:53 +01:00
Wojciech Kozyra 3ffad12681 upgrade eas-cli-local-build-plugin to 0.0.56 2021-12-13 16:19:40 +01:00
Wojciech Kozyra 565867b5a8 Update CHANGELOG.md 2021-12-13 15:16:51 +01:00
Wojciech KozyraandDominik Sokal 303cc82a06 [eas-cli] add build inspect command (#834)
* [eas-cli] add build inspect command

* Apply suggestions from code review

Co-authored-by: Dominik Sokal <[email protected]>

* Apply suggestions from code review

Co-authored-by: Dominik Sokal <[email protected]>

* review feedback

Co-authored-by: Dominik Sokal <[email protected]>
2021-12-13 14:56:25 +01:00
Dominik Sokal 4c2abc0693 fix submission url (#845)
* fix submission url

* update CHANGELOG.md
2021-12-13 14:41:19 +01:00
Dominik SokalandWojciech Kozyra b63167fbae [ENG-2052] ask user to select profile if release does not exist (#829)
* ask user to select profile if release does not exist

* Apply suggestions from code review

Co-authored-by: Wojciech Kozyra <[email protected]>

* update CHANGELOG.md

Co-authored-by: Wojciech Kozyra <[email protected]>
2021-12-13 11:00:09 +01:00
Dominik Sokal 8adb6434bf [ENG-2279] warn when attempting to use EAS CLI on managed SDK < 41 project (#833)
* warn when attempting to use EAS CLI on managed SDK < 41 project

* update CHANGELOG.md

* Update CHANGELOG.md
2021-12-13 09:48:00 +01:00
Dominik Sokal 4c9e407fef [eas-json] use semver for version validation (#832)
* [eas-json] use semver for version validation

* update CHANGELOG.md
2021-12-13 09:44:44 +01:00
jkh ef566b3f9c [eas-cli] fix missing republish message (#839) 2021-12-12 15:34:22 -05:00
jkh 895231ea98 [eas-cli] fix table column names (#840) 2021-12-12 15:34:13 -05:00
jkh c7000a754e [eas-cli] Make default runtime version 1.0.0 for bare workflow (#838) 2021-12-12 15:34:04 -05:00
jkh 9ac8404681 [eas-cli] handle 'eas update' when there are no branches (#837) 2021-12-10 15:18:48 -05:00
Expo CI 6c0b7d5951 update CHANGELOG.md 2021-12-08 20:59:10 +00:00
42 changed files with 1318 additions and 418 deletions

No files matched your search

+33 -3
View File
@@ -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
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.40.0"
"version": "0.41.1"
}
+286 -26
View File
@@ -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 -->
+202
View File
@@ -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": "",
+2 -2
View File
@@ -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",
+10
View File
@@ -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;
}
}
+3 -3
View File
@@ -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 {
+2 -1
View File
@@ -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;
+4 -3
View File
@@ -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,
+45 -6
View File
@@ -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 = [
{
+1 -3
View File
@@ -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 = [
+9 -248
View File
@@ -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 = [
+44 -20
View File
@@ -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
View File
@@ -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
+35
View File
@@ -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);
}
}
+4 -5
View File
@@ -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,
});
+1 -1
View File
@@ -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();
}
+4 -4
View File
@@ -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({
+106 -33
View File
@@ -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();
}
}
+2 -1
View File
@@ -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());
});
+10 -9
View File
@@ -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`);
+5
View File
@@ -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
+1 -1
View File
@@ -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}`