Compare commits

...
38 Commits
Author SHA1 Message Date
Szymon Dziedzic 008279e1dd v5.0.2 2023-08-29 13:15:39 +02:00
Cedric van Putten d00814f84b fix(eas-cli): implement variadic --platform flag when creating EAS updates (#1939)
* fix(eas-cli): implement variadic `--platform` flag when creating EAS updates

* fix(eas-cli): finish todo and set minimum `@expo/[email protected]` version for multiple `--platform` flags during export

* update CHANGELOG.md

* fix(eas-cli): be verbose about `--platform=<flag>` in bundle script
2023-08-29 13:11:49 +02:00
Expo CI 81aa127e28 update CHANGELOG.md 2023-08-28 19:05:12 +00:00
Szymon Dziedzic d321a0bccb v5.0.1 2023-08-28 20:54:10 +02:00
Szymon Dziedzic a1d86549ed [eas-cli] fix eas update passing platform argument incorrectly to expo-cli (#2028)
* [eas-cli] fix `eas update` passing `platform` argument incorrectly to `expo-cli`

* update CHANGELOG.md
2023-08-28 20:51:18 +02:00
Expo CI f7e9af913c update CHANGELOG.md 2023-08-28 14:00:12 +00:00
Szymon Dziedzic 137f9d4ac7 v5.0.0 2023-08-28 14:20:36 +02:00
Szymon Dziedzic 2ac863a9e3 upgrade eas-cli-local-build-plugin to 1.0.39 2023-08-28 14:19:20 +02:00
Quinlan Jung 11be9eba49 [ENG-9685][eas-cli] create a linked channel on build if not exists (#2017)
* [eas-cli] create linked channel on build

* refactor

* update CHANGELOG.md

* amend doc block
2023-08-25 12:07:59 -07:00
Will Schurman b36db15601 [eas-cli][updates] configure updates as well when somebody tries to run a build with channel set (#2016) 2023-08-25 09:14:29 -07:00
Piotr Szeremeta 93c5dc1a32 [ENG-9422] nvmrc support for setting node version (#1998)
* [ENG-9422][eas-cli] Use .nvmrc to set node version

* update CHANGELOG.md
2023-08-25 15:46:55 +02:00
Szymon Dziedzic c86550c0c8 [eas-cli][eas-json] add developmentClient to metadata (#2015)
* [eas-cli][eas-json] add `developmentClient` to metadata

* update CHANGELOG.md
2023-08-25 13:26:33 +02:00
Brent Vatne 36b4296d3c [.github] Add issue template config 2023-08-24 17:09:47 -07:00
Brent Vatne 8c08541ca3 Create SECURITY.md 2023-08-24 17:06:03 -07:00
Will Schurman 5a1406d39c [eas-cli] Add info to EAS Update asset upload process about asset counts and limits (#2013) 2023-08-24 09:22:30 -07:00
Radosław Krzemień 78ea88cc5f [eas-cli] [ENG-7591] Detect not provisioned devices (#2011)
* [eas-cli] Detect not provisioned devices

After selecting devices to be provisioned and making a call to App Store Connect API the result is compared with the list of chosen devices. If any are missing, a message is displayed to the user

See: https://linear.app/expo/issue/ENG-7591/detect-processing-devices-on-apple-developer-portal-and-dont-allow-the

* [eas-cli] Adjust message

Updated the message with the link to the devices page in the Apple Developer portal

See: https://linear.app/expo/issue/ENG-7591/detect-processing-devices-on-apple-developer-portal-and-dont-allow-the

* update CHANGELOG.md
2023-08-24 15:35:48 +02:00
Will Schurman 6cb02d0ccd [eas-cli][eas-update] Support republishing roll back to embedded updates (#2006) 2023-08-15 11:39:24 -04:00
Will Schurman d6c7623718 [eas-cli][eas-update] Add rollback disambiguation command (#2004) 2023-08-15 11:36:18 -04:00
Quinlan Jung 32142b4fe8 [eas-cli] more branch mapping utils (#2003)
* [eas-cli] more branch mapping utils

* update CHANGELOG.md
2023-08-14 14:51:29 -07:00
Will Schurman 6bdee0f395 [eas-cli][update] Only export at most ios and android dist (#2002) 2023-08-14 17:28:06 -04:00
Quinlan Jung baf94a21c3 [eas-cli] more branch map utility functions (#2001)
* [eas-cli] more branch map utility functions

* update CHANGELOG.md

* feedback
2023-08-11 11:35:47 -07:00
Radoslaw Krzemien 162d64bce6 Revert "[eas-cli] Update oclif version"
This reverts commit d16c1fc757.
2023-08-11 12:22:56 +02:00
Radoslaw Krzemien d16c1fc757 [eas-cli] Update oclif version
Updated oclif version

See: https://linear.app/expo/issue/ENG-9175/update-oclif-in-eas-cli
2023-08-11 12:22:17 +02:00
Expo CI e42ea7e2ec update CHANGELOG.md 2023-08-10 10:26:10 +00:00
Szymon Dziedzic 5fea1ad16e v4.1.2 2023-08-10 12:12:49 +02:00
Szymon Dziedzic 2a57a2eb3d upgrade eas-cli-local-build-plugin to 1.0.38 2023-08-10 12:12:41 +02:00
Quinlan Jung 5628b78523 [eas-cli] make branch mapping utility files have no dependencies (#1993)
* [eas-cli] generalize branch mapping utils

* make standalone

* update CHANGELOG.md
2023-08-09 15:32:19 -07:00
Expo CI 19d1e6bffd update CHANGELOG.md 2023-08-08 18:36:49 +00:00
Quinlan Jung 88b068511c v4.1.1 2023-08-08 11:28:16 -07:00
Quinlan Jung 842efcf3ab upgrade eas-cli-local-build-plugin to 1.0.35 2023-08-08 11:28:02 -07:00
Quinlan Jung f5f08aa506 [eas-cli] rollouts websupport (#1997)
* [eas-cli] rollouts websupport

* update CHANGELOG.md

* fix changelog
2023-08-08 11:26:18 -07:00
Expo CI 9f7f412cfb update CHANGELOG.md 2023-08-08 13:19:03 +00:00
Szymon Dziedzic e369ec1e25 v4.1.0 2023-08-08 15:10:39 +02:00
Szymon Dziedzic b353441f31 upgrade eas-cli-local-build-plugin to 1.0.34 2023-08-08 15:10:24 +02:00
Szymon Dziedzic 4db35e73c6 [eas-cli] bump @expo/steps version (#1995) 2023-08-08 15:02:33 +02:00
Szymon Dziedzic 10bc872dc9 [eas-json][eas-cli] add global withoutCredentials property to eas.json (#1994)
* [eas-json][eas-cli] add global `withoutCredentials` property to `eas.json`

* update CHANGELOG.md

* fix tests
2023-08-08 14:13:25 +02:00
Szymon Dziedzic fedc39ea48 [eas-cli] pass iOS credentials to custom builds (#1989)
* [eas-cli] pass iOS credentials to custom builds

* update CHANGELOG.md
2023-08-08 13:55:18 +02:00
Expo CI 6046723769 update CHANGELOG.md 2023-08-08 08:39:19 +00:00
49 changed files with 1092 additions and 294 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Expo Developers Discord
url: https://chat.expo.dev/
about: Join other developers in discussions regarding Expo and EAS.
+57 -1
View File
@@ -10,10 +10,66 @@ This is the log of notable changes to EAS CLI and related packages.
### 🐛 Bug fixes
- Fix `eas channel:*` to work with empty branch mappings. ([#1992](https://github.com/expo/eas-cli/pull/1992) by [@quinlanj](https://github.com/quinlanj))
- Add proper expo cli `--platform` flag handling when exporting updates. ([#1939](https://github.com/expo/eas-cli/pull/1939) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
## [5.0.1](https://github.com/expo/eas-cli/releases/tag/v5.0.1) - 2023-08-28
### 🐛 Bug fixes
- Pass `platform` argument to expo-cli correctly when using the `eas update` command. ([#2028](https://github.com/expo/eas-cli/pull/2028) by [@szdziedzic](https://github.com/szdziedzic))
## [5.0.0](https://github.com/expo/eas-cli/releases/tag/v5.0.0) - 2023-08-28
### 🛠 Breaking changes
- Only export at most ios and android dist for EAS updates. ([#2002](https://github.com/expo/eas-cli/pull/2002) by [@wschurman](https://github.com/wschurman))
### 🎉 New features
- Add rollback disambiguation command. ([#2004](https://github.com/expo/eas-cli/pull/2004) by [@wschurman](https://github.com/wschurman))
- Detect devices that fail to be provisioned, list them to the user and show the explanation message with the link to the devices page to check actual status. ([#2011](https://github.com/expo/eas-cli/pull/2011) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
- Add info to EAS Update asset upload process about asset counts and limits. ([#2013](https://github.com/expo/eas-cli/pull/2013) by [@wschurman](https://github.com/wschurman))
- .nvmrc support for setting node version. ([#1954](https://github.com/expo/eas-cli/pull/1954) by [@khamilowicz](https://github.com/khamilowicz))
### 🐛 Bug fixes
- Support republishing roll back to embedded updates. ([#2006](https://github.com/expo/eas-cli/pull/2006) by [@wschurman](https://github.com/wschurman))
- Configure updates as well when somebody tries to run a build with channel set. ([#2016](https://github.com/expo/eas-cli/pull/2016) by [@wschurman](https://github.com/wschurman))
### 🧹 Chores
- More branch map utility functions. ([#2001](https://github.com/expo/eas-cli/pull/2001) by [@quinlanj](https://github.com/quinlanj))
- More branch mapping utils. ([#2003](https://github.com/expo/eas-cli/pull/2003) by [@quinlanj](https://github.com/quinlanj))
- Create a linked channel on build if not exists. ([#2017](https://github.com/expo/eas-cli/pull/2017) by [@quinlanj](https://github.com/quinlanj))
- Add `developmentClient` to metadata. ([#2015](https://github.com/expo/eas-cli/pull/2015) by [@szdziedzic](https://github.com/szdziedzic))
## [4.1.2](https://github.com/expo/eas-cli/releases/tag/v4.1.2) - 2023-08-10
### 🧹 Chores
- Make branch mapping utility files have no dependencies. ([#1993](https://github.com/expo/eas-cli/pull/1993) by [@quinlanj](https://github.com/quinlanj))
## [4.1.1](https://github.com/expo/eas-cli/releases/tag/v4.1.1) - 2023-08-08
### 🧹 Chores
- Logger to say website support is coming soon for rollouts. ([#1997](https://github.com/expo/eas-cli/pull/1997) by [@quinlanj](https://github.com/quinlanj))
## [4.1.0](https://github.com/expo/eas-cli/releases/tag/v4.1.0) - 2023-08-08
### 🎉 New features
- Pass credentials to custom iOS builds. ([#1989](https://github.com/expo/eas-cli/pull/1989) by [@szdziedzic](https://github.com/szdziedzic))
- Add the `withoutCredentials` option as a common build profile field in `eas.json`. ([#1994](https://github.com/expo/eas-cli/pull/1994) by [@szdziedzic](https://github.com/szdziedzic))
## [4.0.1](https://github.com/expo/eas-cli/releases/tag/v4.0.1) - 2023-08-08
### 🐛 Bug fixes
- Fix `eas channel:*` to work with empty branch mappings. ([#1992](https://github.com/expo/eas-cli/pull/1992) by [@quinlanj](https://github.com/quinlanj))
## [4.0.0](https://github.com/expo/eas-cli/releases/tag/v4.0.0) - 2023-08-07
### 🛠 Breaking changes
+5
View File
@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Instead of opening a GitHub issue for security vulnerabilities, send an email to our secure inbox: secure@expo.io.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "4.0.1",
"version": "5.0.2",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+55 -55
View File
@@ -133,7 +133,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -150,7 +150,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -167,7 +167,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -181,7 +181,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -231,7 +231,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -252,7 +252,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -272,7 +272,7 @@ DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -292,7 +292,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -315,7 +315,7 @@ DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -345,7 +345,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -362,7 +362,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -379,7 +379,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -414,7 +414,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -452,7 +452,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -478,7 +478,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -502,7 +502,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/run.ts)_
_See code: [src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -551,7 +551,7 @@ DESCRIPTION
get the latest version from EAS servers
```
_See code: [src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -570,7 +570,7 @@ DESCRIPTION
update version of an app
```
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -589,7 +589,7 @@ DESCRIPTION
update a version in native code with a value stored on EAS servers
```
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -606,7 +606,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -627,7 +627,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -649,7 +649,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -669,7 +669,7 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -701,7 +701,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally.
```
_See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -724,7 +724,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -745,7 +745,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -762,7 +762,7 @@ DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -776,7 +776,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -796,7 +796,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -817,7 +817,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -838,7 +838,7 @@ DESCRIPTION
rename a registered device
```
_See code: [src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -852,7 +852,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -866,7 +866,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -954,7 +954,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -971,7 +971,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -988,7 +988,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas open`
@@ -1002,7 +1002,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/open.ts)_
_See code: [src/commands/open.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1016,7 +1016,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1038,7 +1038,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -1061,7 +1061,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -1079,7 +1079,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -1093,7 +1093,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push`
@@ -1113,7 +1113,7 @@ DESCRIPTION
read environment secrets from env file and store on the server
```
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/secret/push.ts)_
_See code: [src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit`
@@ -1143,7 +1143,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1176,7 +1176,7 @@ DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1194,7 +1194,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1215,7 +1215,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1237,7 +1237,7 @@ DESCRIPTION
view the recent updates
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1264,7 +1264,7 @@ DESCRIPTION
roll back to an existing update
```
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:view GROUPID`
@@ -1284,7 +1284,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1305,7 +1305,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1325,7 +1325,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1343,7 +1343,7 @@ DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1365,7 +1365,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1382,7 +1382,7 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v4.0.1/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v5.0.2/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "4.0.1",
"version": "5.0.2",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -13,8 +13,8 @@
"@expo/config": "8.1.2",
"@expo/config-plugins": "7.2.4",
"@expo/config-types": "49.0.0",
"@expo/eas-build-job": "1.0.30",
"@expo/eas-json": "4.0.0",
"@expo/eas-build-job": "1.0.39",
"@expo/eas-json": "5.0.0",
"@expo/json-file": "8.2.37",
"@expo/multipart-body-parser": "1.1.0",
"@expo/osascript": "2.0.33",
@@ -27,7 +27,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.31",
"@expo/steps": "1.0.34",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "1.23.2",
"@oclif/plugin-autocomplete": "1.3.10",
+1
View File
@@ -55,4 +55,5 @@ export interface BuildContext<T extends Platform> {
customBuildConfigMetadata?: CustomBuildConfigMetadata;
android: T extends Platform.ANDROID ? AndroidBuildContext : undefined;
ios: T extends Platform.IOS ? IosBuildContext : undefined;
developmentClient: boolean;
}
@@ -60,6 +60,12 @@ export async function createBuildContextAsync<T extends Platform>({
const workflow = await resolveWorkflowAsync(projectDir, platform);
const accountId = account.id;
const runFromCI = getenv.boolish('CI', false);
const developmentClient =
buildProfile.developmentClient ??
(platform === Platform.ANDROID
? (buildProfile as BuildProfile<Platform.ANDROID>)?.gradleCommand === ':app:assembleDebug'
: (buildProfile as BuildProfile<Platform.IOS>)?.buildConfiguration === 'Debug') ??
false;
const credentialsCtx = new CredentialsContext({
projectInfo: { exp, projectId },
@@ -120,6 +126,7 @@ export async function createBuildContextAsync<T extends Platform>({
message,
runFromCI,
customBuildConfigMetadata,
developmentClient,
};
if (platform === Platform.ANDROID) {
const common = commonContext as CommonContext<Platform.ANDROID>;
@@ -55,6 +55,5 @@ export async function ensureIosCredentialsForBuildResignAsync(
}
function shouldProvideCredentials(buildCtx: BuildContext<Platform.IOS>): boolean {
const isCustomBuild = buildCtx.buildProfile.config !== undefined;
return !buildCtx.buildProfile.simulator && !isCustomBuild;
return !buildCtx.buildProfile.simulator && !buildCtx.buildProfile.withoutCredentials;
}
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '1.0.33';
const PLUGIN_PACKAGE_VERSION = '1.0.39';
export enum LocalBuildMode {
/**
+1
View File
@@ -63,6 +63,7 @@ export async function collectMetadataAsync<T extends Platform>(
runFromCI: ctx.runFromCI,
buildMode: ctx.buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
customWorkflowName: ctx.customBuildConfigMetadata?.workflowName,
developmentClient: ctx.developmentClient,
};
return sanitizeMetadata(metadata);
}
@@ -1,3 +1,4 @@
import { ExpoConfig } from '@expo/config-types';
import { Platform, Workflow } from '@expo/eas-build-job';
import {
AppVersionSource,
@@ -13,6 +14,7 @@ import chalk from 'chalk';
import nullthrows from 'nullthrows';
import { Analytics } from '../analytics/AnalyticsManager';
import { createAndLinkChannelAsync, doesChannelExistAsync } from '../channel/queries';
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import {
@@ -38,9 +40,9 @@ import {
import { checkExpoSdkIsSupportedAsync } from '../project/expoSdk';
import { validateMetroConfigForManagedWorkflowAsync } from '../project/metroConfig';
import {
installExpoUpdatesAsync,
isExpoUpdatesInstalledAsDevDependency,
isExpoUpdatesInstalledOrAvailable,
isUsingEASUpdate,
validateAppVersionRuntimePolicySupportAsync,
} from '../project/projectUtils';
import {
@@ -57,6 +59,7 @@ import {
waitToCompleteAsync as waitForSubmissionsToCompleteAsync,
} from '../submit/submit';
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
import { ensureEASUpdateIsConfiguredAsync } from '../update/configure';
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
import { Actor } from '../user/User';
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
@@ -117,6 +120,7 @@ export async function runBuildAndSubmitAsync(
easJsonAccessor,
platforms,
profileName: flags.profile ?? undefined,
projectDir,
});
Log.log(
`Loaded "env" configuration for the "${buildProfiles[0].profileName}" profile: ${
@@ -183,6 +187,7 @@ export async function runBuildAndSubmitAsync(
platforms,
profileName: flags.submitProfile,
type: 'submit',
projectDir,
});
for (const startedBuild of startedBuilds) {
const submitProfile = nullthrows(
@@ -320,11 +325,26 @@ async function prepareAndStartBuildAsync({
);
if (buildProfile.profile.channel) {
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
graphqlClient,
exp: buildCtx.exp,
projectId: buildCtx.projectId,
projectDir,
sdkVersion: buildCtx.exp.sdkVersion,
nonInteractive: flags.nonInteractive,
buildProfile,
});
if (isUsingEASUpdate(buildCtx.exp, buildCtx.projectId)) {
const doesChannelExist = await doesChannelExistAsync(graphqlClient, {
appId: buildCtx.projectId,
channelName: buildProfile.profile.channel,
});
if (!doesChannelExist) {
await createAndLinkChannelAsync(graphqlClient, {
appId: buildCtx.projectId,
channelName: buildProfile.profile.channel,
});
}
}
}
await validateAppVersionRuntimePolicySupportAsync(buildCtx.projectDir, buildCtx.exp);
@@ -447,11 +467,17 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(
}
async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
exp,
graphqlClient,
projectId,
projectDir,
buildProfile,
nonInteractive,
sdkVersion,
}: {
graphqlClient: ExpoGraphqlClient;
exp: ExpoConfig;
projectId: string;
projectDir: string;
buildProfile: ProfileData<'build'>;
nonInteractive: boolean;
@@ -467,18 +493,24 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
);
} else if (nonInteractive) {
Log.warn(
`The build profile "${buildProfile.profileName}" has specified the channel "${buildProfile.profile.channel}", but the "expo-updates" package hasn't been installed. To use channels for your builds, install the "expo-updates" package by running "npx expo install expo-updates".`
`The build profile "${buildProfile.profileName}" has specified the channel "${buildProfile.profile.channel}", but the "expo-updates" package hasn't been installed. To use channels for your builds, install the "expo-updates" package by running "npx expo install expo-updates" followed by "eas update:configure".`
);
} else {
Log.warn(
`The build profile "${buildProfile.profileName}" specifies the channel "${buildProfile.profile.channel}", but the "expo-updates" package is missing. To use channels in your builds, install the "expo-updates" package.`
`The build profile "${buildProfile.profileName}" specifies the channel "${buildProfile.profile.channel}", but the "expo-updates" package is missing. To use channels in your builds, install the "expo-updates" package and run "eas update:configure".`
);
const installExpoUpdates = await confirmAsync({
message: `Would you like to install the "expo-updates" package?`,
message: `Would you like to install the "expo-updates" package and configure EAS Update now?`,
});
if (installExpoUpdates) {
await installExpoUpdatesAsync(projectDir, { silent: false });
Log.withTick('Installed expo-updates');
await ensureEASUpdateIsConfiguredAsync(graphqlClient, {
exp,
projectId,
projectDir,
platform: RequestedPlatform.All,
});
Log.withTick('Installed expo-updates and configured EAS Update.');
throw new Error('Command must be re-run to pick up new updates configuration.');
}
}
}
@@ -0,0 +1,22 @@
import { BranchBasicInfo, ChannelBasicInfo, UpdateChannelInfoWithBranches } from '../utils';
export const testChannelBasicInfo: ChannelBasicInfo = {
id: '9309afc2-9752-40db-8ef7-4abc10744c61',
name: 'production',
branchMapping:
'{"data":[{"branchId":"754bf17f-efc0-46ab-8a59-a03f20e53e9b","branchMappingLogic":{"operand":0.15,"clientKey":"rolloutToken","branchMappingOperator":"hash_lt"}},{"branchId":"6941a8dd-5c0a-48bc-8876-f49c88ed419f","branchMappingLogic":"true"}],"version":0}',
};
export const testBasicBranchInfo1: BranchBasicInfo = {
id: '754bf17f-efc0-46ab-8a59-a03f20e53e9b',
name: 'wrong-channel',
};
export const testBasicBranchInfo2: BranchBasicInfo = {
id: '6941a8dd-5c0a-48bc-8876-f49c88ed419f',
name: 'production',
};
export const channelInfoWithBranches: UpdateChannelInfoWithBranches<BranchBasicInfo> = {
...testChannelBasicInfo,
updateBranches: [testBasicBranchInfo1, testBasicBranchInfo2],
};
@@ -8,19 +8,26 @@ import {
assertVersion,
getAlwaysTrueBranchMapping,
getBranchMapping,
getEmptyBranchMapping,
getStandardBranchId,
isAlwaysTrueBranchMapping,
isEmptyBranchMapping,
} from '../branch-mapping';
import { testChannelObject } from './fixtures';
import { testChannelBasicInfo } from './branch-mapping-fixtures';
describe(assertVersion, () => {
it('throws if the branch mapping is not the correct version', () => {
expect(() => assertVersion(testChannelObject, 5)).toThrowError(BranchMappingValidationError);
expect(() => assertVersion(testChannelBasicInfo, 5)).toThrowError(BranchMappingValidationError);
});
it('asserts the correct version', () => {
assertVersion(testChannelObject, 0);
expect(getBranchMapping(testChannelObject.branchMapping).version).toBe(0);
assertVersion(testChannelBasicInfo, 0);
expect(getBranchMapping(testChannelBasicInfo.branchMapping).version).toBe(0);
});
});
describe(getEmptyBranchMapping, () => {
it('gets an empty branch mapping', () => {
expect(isEmptyBranchMapping(getEmptyBranchMapping())).toBe(true);
});
});
@@ -49,10 +56,12 @@ describe(getAlwaysTrueBranchMapping, () => {
describe(getStandardBranchId, () => {
it('throws if the branch mapping is not a standard mapping', () => {
expect(() => getStandardBranchId(testChannelObject)).toThrowError(BranchMappingValidationError);
expect(() => getStandardBranchId(testChannelBasicInfo)).toThrowError(
BranchMappingValidationError
);
});
it('gets a standard branch id', () => {
const channelObjectWithStandardMapping = { ...testChannelObject };
const channelObjectWithStandardMapping = { ...testChannelBasicInfo };
channelObjectWithStandardMapping.branchMapping = JSON.stringify(standardBranchMapping);
expect(getStandardBranchId(channelObjectWithStandardMapping)).toBe(
standardBranchMapping.data[0].branchId
@@ -1,11 +1,11 @@
import { getUpdateBranch } from '../utils';
import { testChannelObject, testUpdateBranch1 } from './fixtures';
import { channelInfoWithBranches, testBasicBranchInfo1 } from './branch-mapping-fixtures';
describe(getUpdateBranch, () => {
it('gets the update branch', () => {
expect(getUpdateBranch(testChannelObject, '754bf17f-efc0-46ab-8a59-a03f20e53e9b')).toBe(
testUpdateBranch1
expect(getUpdateBranch(channelInfoWithBranches, '754bf17f-efc0-46ab-8a59-a03f20e53e9b')).toBe(
testBasicBranchInfo1
);
expect(() => getUpdateBranch(testChannelObject, 'not-a-branch-ID')).toThrow();
expect(() => getUpdateBranch(channelInfoWithBranches, 'not-a-branch-ID')).toThrow();
});
});
+12 -5
View File
@@ -1,4 +1,4 @@
import { UpdateChannelBasicInfoFragment } from '../graphql/generated';
import { ChannelBasicInfo } from './utils';
// TODO(quin): move this into a common package with www
export type BranchMappingOperator =
@@ -50,6 +50,13 @@ export type EmptyBranchMapping = {
data: [];
};
export function getEmptyBranchMapping(): EmptyBranchMapping {
return {
version: 0,
data: [],
};
}
export function getAlwaysTrueBranchMapping(branchId: string): AlwaysTrueBranchMapping {
return {
version: 0,
@@ -62,17 +69,17 @@ export function getAlwaysTrueBranchMapping(branchId: string): AlwaysTrueBranchMa
};
}
export function hasEmptyBranchMap(channelInfo: UpdateChannelBasicInfoFragment): boolean {
export function hasEmptyBranchMap(channelInfo: ChannelBasicInfo): boolean {
const branchMapping = getBranchMapping(channelInfo.branchMapping);
return isEmptyBranchMapping(branchMapping);
}
export function hasStandardBranchMap(channelInfo: UpdateChannelBasicInfoFragment): boolean {
export function hasStandardBranchMap(channelInfo: ChannelBasicInfo): boolean {
const branchMapping = getBranchMapping(channelInfo.branchMapping);
return isAlwaysTrueBranchMapping(branchMapping);
}
export function getStandardBranchId(channelInfo: UpdateChannelBasicInfoFragment): string {
export function getStandardBranchId(channelInfo: ChannelBasicInfo): string {
const branchMapping = getBranchMapping(channelInfo.branchMapping);
assertAlwaysTrueBranchMapping(branchMapping);
return getBranchIdFromStandardMapping(branchMapping);
@@ -153,7 +160,7 @@ function isVersion(branchMapping: BranchMapping, version: number): boolean {
return branchMapping.version === version;
}
export function assertVersion(channelInfo: UpdateChannelBasicInfoFragment, version: number): void {
export function assertVersion(channelInfo: ChannelBasicInfo, version: number): void {
const branchMapping = getBranchMapping(channelInfo.branchMapping);
if (!isVersion(branchMapping, version)) {
throw new BranchMappingValidationError(
+103
View File
@@ -2,6 +2,8 @@ import chalk from 'chalk';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { createUpdateBranchOnAppAsync } from '../branch/queries';
import { BranchNotFoundError } from '../branch/utils';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { PaginatedQueryOptions } from '../commandUtils/pagination';
import { withErrorHandlingAsync } from '../graphql/client';
@@ -15,13 +17,16 @@ import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/Bra
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
import Log from '../log';
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
import formatFields from '../utils/formatFields';
import { printJsonOnlyOutput } from '../utils/json';
import {
paginatedQueryWithConfirmPromptAsync,
paginatedQueryWithSelectPromptAsync,
} from '../utils/queries';
import { ChannelNotFoundError } from './errors';
import { logChannelDetails } from './print-utils';
import { ChannelBasicInfo } from './utils';
export const CHANNELS_LIMIT = 25;
@@ -264,3 +269,101 @@ export async function ensureChannelExistsAsync(
}
}
}
export async function doesChannelExistAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, channelName }: { appId: string; channelName: string }
): Promise<boolean> {
try {
await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId,
channelName,
});
} catch (err) {
if (err instanceof ChannelNotFoundError) {
return false;
}
throw err;
}
return true;
}
/**
*
* Creates a channel and links it to a branch with the same name.
*
* @param appId the app ID, also known as the project ID
* @param channelName the name of the channel to create
* @param shouldPrintJson print only the JSON output
*/
export async function createAndLinkChannelAsync(
graphqlClient: ExpoGraphqlClient,
{
appId,
channelName,
shouldPrintJson,
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
): Promise<ChannelBasicInfo> {
let branchId: string;
let branchMessage: string;
try {
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId,
name: channelName,
});
branchId = branch.id;
branchMessage = `We found a branch with the same name`;
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId,
name: channelName,
});
branchId = newBranch.id;
branchMessage = `We also went ahead and made a branch with the same name`;
} else {
throw error;
}
}
const {
updateChannel: { createUpdateChannelForApp: newChannel },
} = await createChannelOnAppAsync(graphqlClient, {
appId,
channelName,
branchId,
});
if (!newChannel) {
throw new Error(
`Could not create channel with name ${channelName} on project with id ${appId}`
);
}
if (shouldPrintJson) {
printJsonOnlyOutput(newChannel);
} else {
Log.addNewLineIfNone();
Log.withTick(
`Created a new channel on project ${chalk.bold(
await getDisplayNameForProjectIdAsync(graphqlClient, appId)
)}`
);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: newChannel.id },
])
);
Log.addNewLineIfNone();
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: branchId },
])
);
}
return newChannel;
}
+19 -7
View File
@@ -1,18 +1,30 @@
import { UpdateBranchObject, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
export type ChannelBasicInfo = {
id: string;
name: string;
branchMapping: string;
};
export type BranchBasicInfo = {
id: string;
name: string;
};
function getUpdateBranchNullable(
channel: UpdateChannelObject,
export type UpdateChannelInfoWithBranches<Branch extends BranchBasicInfo> = ChannelBasicInfo & {
updateBranches: Branch[];
};
function getUpdateBranchNullable<Branch extends BranchBasicInfo>(
channel: UpdateChannelInfoWithBranches<Branch>,
branchId: string
): UpdateBranchObject | null {
): Branch | null {
const updateBranches = channel.updateBranches;
const updateBranch = updateBranches.find(branch => branch.id === branchId);
return updateBranch ?? null;
}
export function getUpdateBranch(
channel: UpdateChannelObject,
export function getUpdateBranch<Branch extends BranchBasicInfo>(
channel: UpdateChannelInfoWithBranches<Branch>,
branchId: string
): UpdateBranchObject {
): Branch {
const updateBranch = getUpdateBranchNullable(channel, branchId);
if (!updateBranch) {
throw new Error(
@@ -68,6 +68,7 @@ export default class BuildVersionGetView extends EasCommand {
easJsonAccessor,
platforms,
profileName: flags.profile ?? undefined,
projectDir,
});
const results: { [key in Platform]?: string } = {};
for (const { profile, platform } of buildProfiles) {
@@ -80,6 +80,7 @@ export default class BuildVersionSyncView extends EasCommand {
easJsonAccessor,
platforms,
profileName: flags.profile ?? undefined,
projectDir,
});
for (const profileInfo of buildProfiles) {
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({
@@ -1,16 +1,11 @@
import chalk from 'chalk';
import { createUpdateBranchOnAppAsync } from '../../branch/queries';
import { BranchNotFoundError } from '../../branch/utils';
import { createChannelOnAppAsync } from '../../channel/queries';
import { createAndLinkChannelAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { BranchQuery } from '../../graphql/queries/BranchQuery';
import Log from '../../log';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { enableJsonOutput } from '../../utils/json';
export default class ChannelCreate extends EasCommand {
static override description = 'create a channel';
@@ -60,69 +55,13 @@ export default class ChannelCreate extends EasCommand {
}));
}
let branchId: string;
let branchMessage: string;
try {
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: channelName,
});
branchId = branch.id;
branchMessage = `We found a branch with the same name`;
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId: projectId,
name: channelName,
});
branchId = newBranch.id;
branchMessage = `We also went ahead and made a branch with the same name`;
} else {
throw error;
}
}
const {
updateChannel: { createUpdateChannelForApp: newChannel },
} = await createChannelOnAppAsync(graphqlClient, {
await createAndLinkChannelAsync(graphqlClient, {
appId: projectId,
channelName,
branchId,
shouldPrintJson: jsonFlag,
});
if (!newChannel) {
throw new Error(
`Could not create channel with name ${channelName} on project with id ${projectId}`
);
}
if (jsonFlag) {
printJsonOnlyOutput(newChannel);
} else {
Log.addNewLineIfNone();
Log.withTick(
`Created a new channel on project ${chalk.bold(
await getDisplayNameForProjectIdAsync(graphqlClient, projectId)
)}`
);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: newChannel.id },
])
);
Log.addNewLineIfNone();
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: branchId },
])
);
Log.addNewLineIfNone();
Log.log(chalk.bold('You can now update your app by publishing!'));
}
Log.addNewLineIfNone();
Log.log(chalk.bold('You can now update your app by publishing!'));
}
}
@@ -153,7 +153,7 @@ export default class ChannelRollout extends EasCommand {
} else {
Log.addNewLineIfNone();
Log.warn(
`✨ This command is in Developer Preview and has not been released to production yet`
`✨ This command is in Developer Preview and has not been released to production yet. Website support is coming soon.`
);
Log.addNewLineIfNone();
await new RolloutMainMenu(argsAndFlags).runAsync(ctx);
@@ -47,6 +47,7 @@ export default class MetadataLint extends EasCommand {
easJsonAccessor: EasJsonAccessor.fromProjectPath(projectDir),
platforms: [Platform.IOS],
profileName: flags.profile,
projectDir,
});
if (submitProfiles.length !== 1) {
@@ -49,6 +49,7 @@ export default class MetadataPull extends EasCommand {
easJsonAccessor: EasJsonAccessor.fromProjectPath(projectDir),
platforms: [Platform.IOS],
profileName: flags.profile,
projectDir,
});
if (submitProfiles.length !== 1) {
@@ -47,6 +47,7 @@ export default class MetadataPush extends EasCommand {
easJsonAccessor: EasJsonAccessor.fromProjectPath(projectDir),
platforms: [Platform.IOS],
profileName: flags.profile,
projectDir,
});
if (submitProfiles.length !== 1) {
+1
View File
@@ -118,6 +118,7 @@ export default class Submit extends EasCommand {
easJsonAccessor: EasJsonAccessor.fromProjectPath(projectDir),
platforms,
profileName: flagsWithPlatform.profile,
projectDir,
});
const submissions: SubmissionFragment[] = [];
@@ -57,12 +57,6 @@ jest.mock('../../../fetch');
describe(UpdateRepublish.name, () => {
afterEach(() => vol.reset());
it('errors without --channel, --branch, or --group', async () => {
await expect(new UpdateRepublish([], commandOptions).run()).rejects.toThrow(
'--channel, --branch, or --group must be specified'
);
});
it('errors when providing both --group and --branch', async () => {
const flags = ['--group=1234', '--branch=main'];
@@ -35,6 +35,7 @@ import {
getRuntimeVersionObjectAsync,
getUpdateMessageForCommandAsync,
isUploadedAssetCountAboveWarningThreshold,
platformDisplayNames,
resolveInputDirectoryAsync,
uploadAssetsAsync,
} from '../../project/publish';
@@ -325,6 +326,21 @@ export default class UpdatePublish extends EasCommand {
for (const uploadedAssetPath of uploadResults.uniqueUploadedAssetPaths) {
Log.debug(chalk.dim(`- ${uploadedAssetPath}`));
}
const platformString = (Object.keys(assets) as PublishPlatform[])
.map(platform => {
const collectedAssetForPlatform = nullthrows(assets[platform]);
const totalAssetsForPlatform = collectedAssetForPlatform.assets.length + 1; // launch asset
const assetString = totalAssetsForPlatform === 1 ? 'asset' : 'assets';
return `${totalAssetsForPlatform} ${platformDisplayNames[platform]} ${assetString}`;
})
.join(', ');
Log.withInfo(
`${platformString} (maximum: ${assetLimitPerUpdateGroup} total per update). ${learnMore(
'https://expo.fyi/eas-update-asset-limits',
{ learnMoreMessage: 'Learn more about asset limits.' }
)}`
);
} catch (e) {
assetSpinner.fail('Failed to upload');
throw e;
@@ -1,6 +1,8 @@
import { Platform } from '@expo/config';
import { Flags } from '@oclif/core';
import { selectBranchOnAppAsync } from '../../branch/queries';
import { selectChannelOnAppAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
@@ -151,9 +153,6 @@ export default class UpdateRepublish extends EasCommand {
if (nonInteractive && !groupId) {
throw new Error('Only --group can be used in non-interactive mode');
}
if (!groupId && !(branchName || channelName)) {
throw new Error(`--channel, --branch, or --group must be specified`);
}
const platform =
rawFlags.platform === 'all' ? defaultRepublishPlatforms : ([rawFlags.platform] as Platform[]);
@@ -190,6 +189,10 @@ async function getOrAskUpdatesAsync(
}));
}
if (flags.nonInteractive) {
throw new Error('Must supply --group when in non-interactive mode');
}
if (flags.branchName) {
return await askUpdatesFromBranchNameAsync(graphqlClient, {
...flags,
@@ -206,7 +209,55 @@ async function getOrAskUpdatesAsync(
});
}
throw new Error('--channel, --branch, or --group is required');
const { choice } = await promptAsync({
type: 'select',
message: 'Find update by branch or channel?',
name: 'choice',
choices: [
{ title: 'Branch', value: 'branch' },
{ title: 'Channel', value: 'channel' },
],
});
if (choice === 'channel') {
const { name } = await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to view',
paginatedQueryOptions: {
json: flags.json,
nonInteractive: flags.nonInteractive,
offset: 0,
},
});
return await askUpdatesFromChannelNameAsync(graphqlClient, {
...flags,
channelName: name,
projectId,
});
} else if (choice === 'branch') {
const { name } = await selectBranchOnAppAsync(graphqlClient, {
projectId,
promptTitle: 'Select branch from which to choose update',
displayTextForListItem: updateBranch => ({
title: updateBranch.name,
}),
// discard limit and offset because this query is not their intended target
paginatedQueryOptions: {
json: flags.json,
nonInteractive: flags.nonInteractive,
offset: 0,
},
});
return await askUpdatesFromBranchNameAsync(graphqlClient, {
...flags,
branchName: name,
projectId,
});
} else {
throw new Error('Must choose update via channel or branch');
}
}
/** Ask the user which update needs to be republished by branch name, this requires interactive mode */
@@ -219,10 +270,6 @@ async function askUpdatesFromBranchNameAsync(
nonInteractive,
}: { projectId: string; branchName: string; json: boolean; nonInteractive: boolean }
): Promise<UpdateToRepublish[]> {
if (nonInteractive) {
throw new Error('Must supply --group when in non-interactive mode');
}
const updateGroup = await selectUpdateGroupOnBranchAsync(graphqlClient, {
projectId,
branchName,
@@ -246,10 +293,6 @@ async function askUpdatesFromChannelNameAsync(
nonInteractive,
}: { projectId: string; channelName: string; json: boolean; nonInteractive: boolean }
): Promise<UpdateToRepublish[]> {
if (nonInteractive) {
throw new Error('Must supply --group when in non-interactive mode');
}
const branchName = await getBranchNameFromChannelNameAsync(graphqlClient, projectId, channelName);
return await askUpdatesFromBranchNameAsync(graphqlClient, {
@@ -86,7 +86,11 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
}),
platform: Flags.enum({
char: 'p',
options: [...defaultPublishPlatforms, 'all'],
options: [
// TODO: Add web when it's fully supported
...defaultPublishPlatforms,
'all',
],
default: 'all',
required: false,
}),
@@ -0,0 +1,42 @@
import { Flags } from '@oclif/core';
import EasCommand from '../../commandUtils/EasCommand';
import { promptAsync } from '../../prompts';
import UpdateRepublish from './republish';
import UpdateRollBackToEmbedded from './roll-back-to-embedded';
export default class UpdateRollback extends EasCommand {
static override description = 'roll back to an embedded update or an existing update';
static override hidden = true;
static override flags = {
'private-key-path': Flags.string({
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory.`,
required: false,
}),
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(UpdateRollback);
const { choice } = await promptAsync({
type: 'select',
message: 'Which type of update would you like to roll back to?',
name: 'choice',
choices: [
{ title: 'Published Update', value: 'published' },
{ title: 'Embedded Update', value: 'embedded' },
],
});
const privateKeyPathArg = flags['private-key-path']
? ['--private-key-path', flags['private-key-path']]
: [];
if (choice === 'published') {
await UpdateRepublish.run(privateKeyPathArg);
} else {
await UpdateRollBackToEmbedded.run(privateKeyPathArg);
}
}
}
@@ -177,7 +177,23 @@ export class SetUpAdhocProvisioningProfile {
);
}
// 6. Create (or update) app build credentials
// 6. Compare selected devices with the ones actually provisioned
const diffList = differenceBy(
chosenDevices,
appleProvisioningProfile.appleDevices,
'identifier'
);
if (diffList && diffList.length > 0) {
Log.warn(`Failed to provision ${diffList.length} of the selected devices:`);
for (const missingDevice of diffList) {
Log.warn(`- ${formatDeviceLabel(missingDevice)}`);
}
Log.log(
'Most commonly devices fail to to be provisioned while they are still being processed by Apple, which can take up to 24-72 hours. Check your Apple Developer Portal page at https://developer.apple.com/account/resources/devices/list, the devices in "Processing" status cannot be provisioned yet'
);
}
// 7. Create (or update) app build credentials
assert(appleProvisioningProfile);
return await assignBuildCredentialsAsync(
ctx,
@@ -1,4 +1,38 @@
import { doUDIDsMatch } from '../SetUpAdhocProvisioningProfile';
import { Env } from '@expo/eas-build-job';
import { EasJson } from '@expo/eas-json';
import { Analytics } from '../../../../analytics/AnalyticsManager';
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
import {
Account,
AppleAppIdentifierFragment,
AppleDevice,
AppleDeviceClass,
AppleDeviceFragment,
AppleDistributionCertificateFragment,
IosAppBuildCredentialsFragment,
} from '../../../../graphql/generated';
import Log from '../../../../log';
import { getApplePlatformFromTarget } from '../../../../project/ios/target';
import { Actor } from '../../../../user/User';
import { CredentialsContext, CredentialsContextProjectInfo } from '../../../context';
import { ProvisioningProfile } from '../../appstore/Credentials.types';
import { ApplePlatform } from '../../appstore/constants';
import { assignBuildCredentialsAsync, getBuildCredentialsAsync } from '../BuildCredentialsUtils';
import { chooseDevicesAsync } from '../DeviceUtils';
import { SetUpAdhocProvisioningProfile, doUDIDsMatch } from '../SetUpAdhocProvisioningProfile';
jest.mock('../BuildCredentialsUtils');
jest.mock('../../../context');
jest.mock('../../../ios/api/GraphqlClient');
jest.mock('../DeviceUtils', () => {
return {
__esModule: true,
chooseDevicesAsync: jest.fn(),
formatDeviceLabel: jest.requireActual('../DeviceUtils').formatDeviceLabel,
};
});
jest.mock('../../../../project/ios/target');
describe(doUDIDsMatch, () => {
it('return false if UDIDs do not match', () => {
@@ -12,3 +46,104 @@ describe(doUDIDsMatch, () => {
expect(doUDIDsMatch(udidsA, udidsB)).toBe(true);
});
});
describe('runWithDistributionCertificateAsync', () => {
beforeEach(() => {
jest.resetAllMocks();
});
const setUpAdhocProvisioningProfile = new SetUpAdhocProvisioningProfile({
app: { account: {} as Account, projectName: 'projName', bundleIdentifier: 'bundleId' },
target: { targetName: 'targetName', bundleIdentifier: 'bundleId', entitlements: {} },
});
describe('compare chosen and provisioned devices', () => {
describe('not all devices provisioned', () => {
it('displays warning to the user and lists the missing devices', async () => {
const { ctx, distCert } = setUpTest();
jest.mocked(getBuildCredentialsAsync).mockResolvedValue({
provisioningProfile: {
appleTeam: {},
appleDevices: [{ identifier: 'id1' }],
developerPortalIdentifier: 'provisioningProfileId',
},
} as IosAppBuildCredentialsFragment);
const LogWarnSpy = jest.spyOn(Log, 'warn');
const LogLogSpy = jest.spyOn(Log, 'log');
const result = await setUpAdhocProvisioningProfile.runWithDistributionCertificateAsync(
ctx,
distCert
);
expect(result).toEqual({} as IosAppBuildCredentialsFragment);
expect(LogWarnSpy).toHaveBeenCalledTimes(3);
expect(LogWarnSpy).toHaveBeenCalledWith('Failed to provision 2 of the selected devices:');
expect(LogWarnSpy).toHaveBeenCalledWith('- id2 (iPhone) (Device 2)');
expect(LogWarnSpy).toHaveBeenCalledWith('- id3 (Mac) (Device 3)');
expect(LogLogSpy).toHaveBeenCalledTimes(1);
expect(LogLogSpy).toHaveBeenCalledWith(
'Most commonly devices fail to to be provisioned while they are still being processed by Apple, which can take up to 24-72 hours. Check your Apple Developer Portal page at https://developer.apple.com/account/resources/devices/list, the devices in "Processing" status cannot be provisioned yet'
);
});
});
describe('all devices provisioned', () => {
it('does not display warning', async () => {
const { ctx, distCert } = setUpTest();
jest.mocked(getBuildCredentialsAsync).mockResolvedValue({
provisioningProfile: {
appleTeam: {},
appleDevices: [{ identifier: 'id1' }, { identifier: 'id2' }, { identifier: 'id3' }],
developerPortalIdentifier: 'provisioningProfileId',
},
} as IosAppBuildCredentialsFragment);
const LogWarnSpy = jest.spyOn(Log, 'warn');
const LogLogSpy = jest.spyOn(Log, 'log');
const result = await setUpAdhocProvisioningProfile.runWithDistributionCertificateAsync(
ctx,
distCert
);
expect(result).toEqual({} as IosAppBuildCredentialsFragment);
expect(LogWarnSpy).not.toHaveBeenCalled();
expect(LogLogSpy).not.toHaveBeenCalled();
});
});
});
});
function setUpTest(): { ctx: CredentialsContext; distCert: AppleDistributionCertificateFragment } {
const ctx = jest.mocked(
new CredentialsContext(
{} as {
projectInfo: CredentialsContextProjectInfo | null;
easJsonCliConfig?: EasJson['cli'];
nonInteractive: boolean;
projectDir: string;
user: Actor;
graphqlClient: ExpoGraphqlClient;
analytics: Analytics;
env?: Env;
}
)
);
Object.defineProperty(ctx, 'ios', { value: jest.mock('../../../ios/api/GraphqlClient') });
ctx.ios.getDevicesForAppleTeamAsync = jest
.fn()
.mockResolvedValue([
{ identifier: 'id1' },
{ identifier: 'id2' },
{ identifier: 'id3' },
] as AppleDeviceFragment[]);
jest.mocked(chooseDevicesAsync).mockResolvedValue([
{ identifier: 'id1', name: 'Device 1', deviceClass: AppleDeviceClass.Ipad },
{ identifier: 'id2', name: 'Device 2', deviceClass: AppleDeviceClass.Iphone },
{ identifier: 'id3', name: 'Device 3', deviceClass: AppleDeviceClass.Mac },
] as AppleDevice[]);
// @ts-ignore
jest.mocked(getApplePlatformFromTarget).mockResolvedValue(ApplePlatform.IOS);
Object.defineProperty(ctx, 'appStore', { value: jest.mock('../../appstore/AppStoreApi') });
ctx.appStore.createOrReuseAdhocProvisioningProfileAsync = jest.fn().mockResolvedValue({
provisioningProfileId: 'provisioningProfileId',
} as ProvisioningProfile);
ctx.ios.createOrGetExistingAppleAppIdentifierAsync = jest
.fn()
.mockResolvedValue({} as AppleAppIdentifierFragment);
jest.mocked(assignBuildCredentialsAsync).mockResolvedValue({} as IosAppBuildCredentialsFragment);
const distCert = {} as AppleDistributionCertificateFragment;
return { ctx, distCert };
}
+4
View File
@@ -57,6 +57,10 @@ export default class Log {
Log.consoleLog(chalk.green(figures.tick), ...args);
}
public static withInfo(...args: any[]): void {
Log.consoleLog(chalk.green(figures.info), ...args);
}
private static consoleLog(...args: any[]): void {
Log.updateIsLastLineNewLine(args);
// eslint-disable-next-line no-console
+55 -18
View File
@@ -25,7 +25,11 @@ import { promptAsync } from '../prompts';
import { getBranchNameFromChannelNameAsync } from '../update/getBranchNameFromChannelNameAsync';
import { formatUpdateMessage, truncateString as truncateUpdateMessage } from '../update/utils';
import { PresignedPost, uploadWithPresignedPostWithRetryAsync } from '../uploads';
import { expoCommandAsync, shouldUseVersionedExpoCLI } from '../utils/expoCli';
import {
expoCommandAsync,
shouldUseVersionedExpoCLI,
shouldUseVersionedExpoCLIWithExplicitPlatforms,
} from '../utils/expoCli';
import chunk from '../utils/expodash/chunk';
import { truthy } from '../utils/expodash/filter';
import uniqBy from '../utils/expodash/uniqBy';
@@ -184,7 +188,7 @@ export async function buildBundlesAsync({
}: {
projectDir: string;
inputDir: string;
exp: Pick<ExpoConfig, 'sdkVersion'>;
exp: Pick<ExpoConfig, 'sdkVersion' | 'web'>;
platformFlag: ExpoCLIExportPlatformFlag;
clearCache?: boolean;
}): Promise<void> {
@@ -193,20 +197,9 @@ export async function buildBundlesAsync({
throw new Error('Could not locate package.json');
}
if (shouldUseVersionedExpoCLI(projectDir, exp)) {
await expoCommandAsync(projectDir, [
'export',
'--output-dir',
inputDir,
'--dump-sourcemap',
'--dump-assetmap',
'--platform',
platformFlag,
...(clearCache ? ['--clear'] : []),
]);
} else {
// Legacy global Expo CLI
await expoCommandAsync(projectDir, [
// Legacy global Expo CLI
if (!shouldUseVersionedExpoCLI(projectDir, exp)) {
return await expoCommandAsync(projectDir, [
'export',
'--output-dir',
inputDir,
@@ -214,11 +207,49 @@ export async function buildBundlesAsync({
'--non-interactive',
'--dump-sourcemap',
'--dump-assetmap',
'--platform',
platformFlag,
`--platform=${platformFlag}`,
...(clearCache ? ['--clear'] : []),
]);
}
// Versioned Expo CLI, with multiple platform flag support
if (shouldUseVersionedExpoCLIWithExplicitPlatforms(projectDir)) {
// When creating EAS updates, we don't want to build a web bundle
const platformArgs =
platformFlag === 'all'
? ['--platform', 'ios', '--platform', 'android']
: ['--platform', platformFlag];
return await expoCommandAsync(projectDir, [
'export',
'--output-dir',
inputDir,
'--dump-sourcemap',
'--dump-assetmap',
...platformArgs,
...(clearCache ? ['--clear'] : []),
]);
}
// Versioned Expo CLI, without multiple platform flag support
// Warn users about potential export issues when using Metro web
// See: https://github.com/expo/expo/pull/23621
if (exp.web?.bundler === 'metro') {
Log.warn('Exporting bundle for all platforms, including Metro web.');
Log.warn(
'If your app is incompatible with web, remove the "expo.web.bundler" property from your app manifest, or upgrade to the latest Expo SDK.'
);
}
return await expoCommandAsync(projectDir, [
'export',
'--output-dir',
inputDir,
'--dump-sourcemap',
'--dump-assetmap',
`--platform=${platformFlag}`,
...(clearCache ? ['--clear'] : []),
]);
}
export async function resolveInputDirectoryAsync(
@@ -682,3 +713,9 @@ export function getRuntimeToPlatformMappingFromRuntimeVersions(
}
return runtimeToPlatformMapping;
}
export const platformDisplayNames: Record<Platform, string> = {
android: 'Android',
ios: 'iOS',
web: 'Web',
};
@@ -1,10 +1,11 @@
import { v4 as uuidv4 } from 'uuid';
import {
testChannelObject,
testUpdateBranch1,
testUpdateBranch2,
} from '../../channel/__tests__/fixtures';
channelInfoWithBranches,
testBasicBranchInfo1,
testBasicBranchInfo2,
testChannelBasicInfo,
} from '../../channel/__tests__/branch-mapping-fixtures';
import {
BranchMappingOperator,
BranchMappingValidationError,
@@ -17,6 +18,7 @@ import {
assertRolloutBranchMapping,
composeRollout,
createRolloutBranchMapping,
doesTargetRollout,
editRolloutBranchMapping,
getRollout,
getRolloutInfo,
@@ -32,18 +34,29 @@ import {
standardBranchMapping,
} from './fixtures';
describe(doesTargetRollout, () => {
it('detects whether a runtime targets a constrained rollout', () => {
expect(doesTargetRollout(rolloutBranchMapping, '1.0.0')).toBe(true);
expect(doesTargetRollout(rolloutBranchMapping, '2.0.0')).toBe(false);
});
it('should always return true if the rollout is unconstrained', () => {
expect(doesTargetRollout(rolloutBranchMappingLegacy, '1.0.0')).toBe(true);
expect(doesTargetRollout(rolloutBranchMappingLegacy, '2.0.0')).toBe(true);
});
});
describe(composeRollout, () => {
it('composes a rollout', () => {
const rollout = getRollout(testChannelObject);
const rolloutInfo = getRolloutInfo(testChannelObject);
const composedRollout = composeRollout(rolloutInfo, testUpdateBranch2, testUpdateBranch1);
const rollout = getRollout(channelInfoWithBranches);
const rolloutInfo = getRolloutInfo(testChannelBasicInfo);
const composedRollout = composeRollout(rolloutInfo, testBasicBranchInfo2, testBasicBranchInfo1);
expect(composedRollout).toEqual(rollout);
});
it('throws if the branches to not match the rollout info', () => {
const rolloutInfo = getRolloutInfo(testChannelObject);
expect(() => composeRollout(rolloutInfo, testUpdateBranch1, testUpdateBranch2)).toThrowError(
BranchMappingValidationError
);
const rolloutInfo = getRolloutInfo(testChannelBasicInfo);
expect(() =>
composeRollout(rolloutInfo, testBasicBranchInfo1, testBasicBranchInfo2)
).toThrowError(BranchMappingValidationError);
});
});
@@ -119,33 +132,33 @@ describe(isConstrainedRollout, () => {
describe(getRollout, () => {
it('doesnt get the mapping if it isnt a rollout', () => {
const notRollout = { ...testChannelObject };
const notRollout = { ...channelInfoWithBranches };
notRollout.branchMapping = JSON.stringify(standardBranchMapping);
expect(() => getRollout(notRollout)).toThrowError();
});
it('gets a constrained rollout', () => {
const constrainedRollout = { ...testChannelObject };
const constrainedRollout = { ...channelInfoWithBranches };
const rolloutBranchMappingWithCorrectBranch = { ...rolloutBranchMapping };
rolloutBranchMappingWithCorrectBranch.data[0].branchId = testUpdateBranch1.id;
rolloutBranchMappingWithCorrectBranch.data[1].branchId = testUpdateBranch2.id;
rolloutBranchMappingWithCorrectBranch.data[0].branchId = testBasicBranchInfo1.id;
rolloutBranchMappingWithCorrectBranch.data[1].branchId = testBasicBranchInfo2.id;
constrainedRollout.branchMapping = JSON.stringify(rolloutBranchMapping);
const rollout = getRollout(constrainedRollout);
expect(rollout.percentRolledOut).toEqual(10);
expect(isConstrainedRolloutInfo(rollout)).toBe(true);
expect(rollout.defaultBranch).toEqual(testUpdateBranch2);
expect(rollout.rolledOutBranch).toEqual(testUpdateBranch1);
expect(rollout.defaultBranchId).toEqual(testUpdateBranch2.id);
expect(rollout.rolledOutBranchId).toEqual(testUpdateBranch1.id);
expect(rollout.defaultBranch).toEqual(testBasicBranchInfo2);
expect(rollout.rolledOutBranch).toEqual(testBasicBranchInfo1);
expect(rollout.defaultBranchId).toEqual(testBasicBranchInfo2.id);
expect(rollout.rolledOutBranchId).toEqual(testBasicBranchInfo1.id);
});
it('gets a legacy rollout', () => {
const rollout = getRollout(testChannelObject);
const rollout = getRollout(channelInfoWithBranches);
expect(rollout.percentRolledOut).toEqual(15);
expect(isLegacyRolloutInfo(rollout)).toBe(true);
expect(rollout.defaultBranch).toEqual(testUpdateBranch2);
expect(rollout.rolledOutBranch).toEqual(testUpdateBranch1);
expect(rollout.defaultBranchId).toEqual(testUpdateBranch2.id);
expect(rollout.rolledOutBranchId).toEqual(testUpdateBranch1.id);
expect(rollout.defaultBranch).toEqual(testBasicBranchInfo2);
expect(rollout.rolledOutBranch).toEqual(testBasicBranchInfo1);
expect(rollout.defaultBranchId).toEqual(testBasicBranchInfo2.id);
expect(rollout.rolledOutBranchId).toEqual(testBasicBranchInfo1.id);
});
});
@@ -5,7 +5,11 @@ import { getAlwaysTrueBranchMapping } from '../../channel/branch-mapping';
import { updateChannelBranchMappingAsync } from '../../commands/channel/edit';
import { EASUpdateAction, EASUpdateContext } from '../../eas-update/utils';
import { UpdateChannelBasicInfoFragment } from '../../graphql/generated';
import { ChannelQuery, UpdateChannelObject } from '../../graphql/queries/ChannelQuery';
import {
ChannelQuery,
UpdateBranchObject,
UpdateChannelObject,
} from '../../graphql/queries/ChannelQuery';
import Log from '../../log';
import { confirmAsync, promptAsync } from '../../prompts';
import { republishAsync } from '../../update/republish';
@@ -103,7 +107,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
});
}
async selectOutcomeAsync(rollout: Rollout): Promise<EndOutcome> {
async selectOutcomeAsync(rollout: Rollout<UpdateBranchObject>): Promise<EndOutcome> {
const { rolledOutBranch, percentRolledOut, defaultBranch } = rollout;
const rolledOutUpdateGroup = rolledOutBranch.updateGroups[0];
const defaultUpdateGroup = defaultBranch.updateGroups[0];
@@ -142,7 +146,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
async performOutcomeAsync(
ctx: EASUpdateContext,
rollout: Rollout,
rollout: Rollout<UpdateBranchObject>,
outcome: EndOutcome
): Promise<UpdateChannelBasicInfoFragment> {
const { graphqlClient, app } = ctx;
@@ -184,7 +188,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
async confirmOutcomeAsync(
ctx: EASUpdateContext,
selectedOutcome: EndOutcome,
rollout: Rollout
rollout: Rollout<UpdateBranchObject>
): Promise<boolean> {
const { nonInteractive } = ctx;
if (nonInteractive) {
+47 -22
View File
@@ -1,5 +1,3 @@
import assert from 'assert';
import {
BranchMapping,
BranchMappingAlwaysTrue,
@@ -16,19 +14,24 @@ import {
isAndStatement,
isStatement,
} from '../channel/branch-mapping';
import { getUpdateBranch } from '../channel/utils';
import { UpdateChannelBasicInfoFragment } from '../graphql/generated';
import { UpdateBranchObject, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
import {
BranchBasicInfo,
ChannelBasicInfo,
UpdateChannelInfoWithBranches,
getUpdateBranch,
} from '../channel/utils';
export type Rollout = LegacyRollout | ConstrainedRollout;
export type Rollout<Branch extends BranchBasicInfo> =
| LegacyRollout<Branch>
| ConstrainedRollout<Branch>;
export type RolloutInfo = LegacyRolloutInfo | ConstrainedRolloutInfo;
type ConstrainedRollout = LegacyRollout & {
type ConstrainedRollout<Branch extends BranchBasicInfo> = LegacyRollout<Branch> & {
runtimeVersion: string;
};
type LegacyRollout = {
rolledOutBranch: UpdateBranchObject;
defaultBranch: UpdateBranchObject;
type LegacyRollout<Branch extends BranchBasicInfo> = {
rolledOutBranch: Branch;
defaultBranch: Branch;
} & LegacyRolloutInfo;
type ConstrainedRolloutInfo = LegacyRolloutInfo & {
@@ -95,11 +98,13 @@ export function isConstrainedRolloutInfo(rollout: RolloutInfo): rollout is Const
return 'runtimeVersion' in rollout;
}
export function isConstrainedRollout(rollout: Rollout): rollout is ConstrainedRollout {
export function isConstrainedRollout<Branch extends BranchBasicInfo>(
rollout: Rollout<Branch>
): rollout is ConstrainedRollout<Branch> {
return isConstrainedRolloutInfo(rollout);
}
export function getRolloutInfo(basicChannelInfo: UpdateChannelBasicInfoFragment): RolloutInfo {
export function getRolloutInfo(basicChannelInfo: ChannelBasicInfo): RolloutInfo {
const rolloutBranchMapping = getRolloutBranchMapping(basicChannelInfo.branchMapping);
return getRolloutInfoFromBranchMapping(rolloutBranchMapping);
}
@@ -114,13 +119,18 @@ export function getRolloutInfoFromBranchMapping(branchMapping: RolloutBranchMapp
const nodesFromStatement = getNodesFromStatement(statementNode);
const runtimeVersionNode = nodesFromStatement.find(isRuntimeVersionNode);
assert(runtimeVersionNode, 'Runtime version node must be defined.');
if (!runtimeVersionNode) {
throw new BranchMappingValidationError('Runtime version node must be defined.');
}
assertNodeObject(runtimeVersionNode);
const runtimeVersion = runtimeVersionNode.operand;
assertString(runtimeVersion);
const rolloutNode = nodesFromStatement.find(isRolloutNode);
assert(rolloutNode, 'Rollout node must be defined.');
if (!rolloutNode) {
throw new BranchMappingValidationError('Rollout node must be defined.');
}
assertNodeObject(rolloutNode);
const operand = rolloutNode.operand;
assertNumber(operand);
@@ -143,21 +153,23 @@ export function getRolloutInfoFromBranchMapping(branchMapping: RolloutBranchMapp
}
}
export function getRollout(channel: UpdateChannelObject): Rollout {
export function getRollout<Branch extends BranchBasicInfo>(
channel: UpdateChannelInfoWithBranches<Branch>
): Rollout<Branch> {
const rolloutBranchMapping = getRolloutBranchMapping(channel.branchMapping);
const rolledOutBranchId = rolloutBranchMapping.data[0].branchId;
const rolledOutBranch = getUpdateBranch(channel, rolledOutBranchId);
const defaultBranchId = rolloutBranchMapping.data[1].branchId;
const defaultBranch = getUpdateBranch(channel, defaultBranchId);
const rolloutInfo = getRolloutInfo(channel);
return composeRollout(rolloutInfo, defaultBranch, rolledOutBranch);
return composeRollout<Branch>(rolloutInfo, defaultBranch, rolledOutBranch);
}
export function composeRollout(
export function composeRollout<Branch extends BranchBasicInfo>(
rolloutInfo: RolloutInfo,
defaultBranch: UpdateBranchObject,
rolledOutBranch: UpdateBranchObject
): Rollout {
defaultBranch: Branch,
rolledOutBranch: Branch
): Rollout<Branch> {
if (rolloutInfo.defaultBranchId !== defaultBranch.id) {
throw new BranchMappingValidationError(
`Default branch id must match. Received: ${JSON.stringify(rolloutInfo)} ${defaultBranch.id}`
@@ -240,11 +252,22 @@ export function isRolloutBranchMapping(
return isUnconstrainedRollout(branchMapping) || isRtvConstrainedRollout(branchMapping);
}
export function isRollout(channelInfo: UpdateChannelBasicInfoFragment): boolean {
export function isRollout(channelInfo: ChannelBasicInfo): boolean {
const branchMapping = getBranchMapping(channelInfo.branchMapping);
return isRolloutBranchMapping(branchMapping);
}
export function doesTargetRollout(
branchMapping: RolloutBranchMapping,
runtimeVersion: string
): boolean {
const rolloutInfo = getRolloutInfoFromBranchMapping(branchMapping);
if (!isConstrainedRolloutInfo(rolloutInfo)) {
return true;
}
return rolloutInfo.runtimeVersion === runtimeVersion;
}
export function createRolloutBranchMapping({
defaultBranchId,
rolloutBranchId,
@@ -302,7 +325,9 @@ function editRtvConstrainedRollout(
const nodesFromStatement = getNodesFromStatement(statementNode);
const rolloutNode = nodesFromStatement.find(isRolloutNode);
assert(rolloutNode, 'Rollout node must be defined.');
if (!rolloutNode) {
throw new BranchMappingValidationError('Rollout node must be defined.');
}
rolloutNode.operand = percent / 100;
return newBranchMapping;
}
+9 -2
View File
@@ -1,6 +1,10 @@
import chalk from 'chalk';
import { RuntimeFragment, UpdateFragment } from '../graphql/generated';
import {
RuntimeFragment,
UpdateBranchBasicInfoFragment,
UpdateFragment,
} from '../graphql/generated';
import { UpdateBranchObject, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
import Log from '../log';
import { promptAsync } from '../prompts';
@@ -13,7 +17,10 @@ export function printRollout(channel: UpdateChannelObject): void {
displayRolloutDetails(channel.name, rollout);
}
export function displayRolloutDetails(channelName: string, rollout: Rollout): void {
export function displayRolloutDetails(
channelName: string,
rollout: Rollout<UpdateBranchBasicInfoFragment>
): void {
const rolledOutPercent = rollout.percentRolledOut;
Log.newLine();
Log.log(chalk.bold('🚀 Rollout:'));
+43 -21
View File
@@ -5,14 +5,16 @@ import nullthrows from 'nullthrows';
import { getUpdateGroupUrl } from '../build/utils/url';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import fetch from '../fetch';
import { Update, UpdateInfoGroup } from '../graphql/generated';
import { UpdateFragment, UpdateInfoGroup } from '../graphql/generated';
import { PublishMutation } from '../graphql/mutations/PublishMutation';
import Log, { link } from '../log';
import { ora } from '../ora';
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
import {
CodeSigningInfo,
checkDirectiveBodyAgainstUpdateInfoGroup,
checkManifestBodyAgainstUpdateInfoGroup,
getDirectiveBodyAsync,
getManifestBodyAsync,
signBody,
} from '../utils/code-signing';
@@ -23,15 +25,7 @@ export type UpdateToRepublish = {
groupId: string;
branchId: string;
branchName: string;
} & Pick<
Update,
| 'message'
| 'runtimeVersion'
| 'manifestFragment'
| 'platform'
| 'gitCommitHash'
| 'codeSigningInfo'
>;
} & UpdateFragment;
/**
* @param updatesToPublish The update group to republish
@@ -65,6 +59,13 @@ export async function republishAsync({
update.branchName === arbitraryUpdate.branchName &&
update.runtimeVersion === arbitraryUpdate.runtimeVersion;
assert(updatesToPublish.every(isSameGroup), 'All updates must belong to the same update group');
assert(
updatesToPublish.every(u => u.isRollBackToEmbedded) ||
updatesToPublish.every(u => !u.isRollBackToEmbedded),
'All updates must either be roll back to embedded updates or not'
);
const { runtimeVersion } = arbitraryUpdate;
// If codesigning was created for the original update, we need to add it to the republish.
@@ -96,16 +97,25 @@ export async function republishAsync({
let updatesRepublished: Awaited<ReturnType<typeof PublishMutation.publishUpdateGroupAsync>>;
try {
const updateInfoGroup = Object.fromEntries(
updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)])
);
const arbitraryUpdate = updatesToPublish[0];
const objectToMergeIn = arbitraryUpdate.isRollBackToEmbedded
? {
rollBackToEmbeddedInfoGroup: Object.fromEntries(
updatesToPublish.map(update => [update.platform, true])
),
}
: {
updateInfoGroup: Object.fromEntries(
updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)])
),
};
updatesRepublished = await PublishMutation.publishUpdateGroupAsync(graphqlClient, [
{
branchId: targetBranchId,
runtimeVersion,
message: updateMessage,
updateInfoGroup,
...objectToMergeIn,
gitCommitHash: updatesToPublish[0].gitCommitHash,
awaitingCodeSigningInfo: !!codeSigningInfo,
},
@@ -120,19 +130,31 @@ export async function republishAsync({
method: 'GET',
headers: { accept: 'multipart/mixed' },
});
const manifestBody = nullthrows(await getManifestBodyAsync(response));
checkManifestBodyAgainstUpdateInfoGroup(
manifestBody,
nullthrows(nullthrows(updateInfoGroup)[newUpdate.platform as keyof UpdateInfoGroup])
);
let signature;
if (newUpdate.isRollBackToEmbedded) {
const directiveBody = nullthrows(await getDirectiveBodyAsync(response));
const manifestSignature = signBody(manifestBody, codeSigningInfo);
checkDirectiveBodyAgainstUpdateInfoGroup(directiveBody);
signature = signBody(directiveBody, codeSigningInfo);
} else {
const manifestBody = nullthrows(await getManifestBodyAsync(response));
checkManifestBodyAgainstUpdateInfoGroup(
manifestBody,
nullthrows(
nullthrows(objectToMergeIn.updateInfoGroup)[
newUpdate.platform as keyof UpdateInfoGroup
]
)
);
signature = signBody(manifestBody, codeSigningInfo);
}
await PublishMutation.setCodeSigningInfoAsync(graphqlClient, newUpdate.id, {
alg: codeSigningInfo.codeSigningMetadata.alg,
keyid: codeSigningInfo.codeSigningMetadata.keyid,
sig: manifestSignature,
sig: signature,
});
})
);
@@ -1,5 +1,8 @@
import { Platform } from '@expo/eas-build-job';
import { BuildProfile, EasJsonAccessor, EasJsonUtils, errors } from '@expo/eas-json';
import fs from 'fs-extra';
import { vol } from 'memfs';
import os from 'os';
import Log from '../../log';
import { selectAsync } from '../../prompts';
@@ -9,6 +12,7 @@ import {
maybePrintBuildProfileDeprecationWarningsAsync,
} from '../profiles';
jest.mock('fs');
jest.mock('../../prompts');
jest.mock('@expo/eas-json', () => {
const actual = jest.requireActual('@expo/eas-json');
@@ -32,6 +36,7 @@ const getBuildProfileDeprecationWarningsAsync = jest.spyOn(
);
const newLineSpy = jest.spyOn(Log, 'newLine');
const warnSpy = jest.spyOn(Log, 'warn');
const projectDir = '/app';
describe(getProfilesAsync, () => {
afterEach(() => {
@@ -51,6 +56,7 @@ describe(getProfilesAsync, () => {
platforms: [Platform.ANDROID, Platform.IOS],
profileName: undefined,
type: 'build',
projectDir,
});
expect(result[0].profileName).toBe('production');
@@ -71,6 +77,7 @@ describe(getProfilesAsync, () => {
platforms: [Platform.ANDROID],
profileName: undefined,
type: 'build',
projectDir,
})
).rejects.toThrowError(errors.MissingProfileError);
});
@@ -86,6 +93,7 @@ describe(getProfilesAsync, () => {
platforms: [Platform.ANDROID, Platform.IOS],
profileName: 'custom-profile',
type: 'build',
projectDir,
});
expect(result[0].profileName).toBe('custom-profile');
@@ -98,6 +106,52 @@ describe(getProfilesAsync, () => {
expect(getBuildProfileAsync).toBeCalledWith(easJsonAccessor, Platform.IOS, 'custom-profile');
});
describe('node version', () => {
const nodeVersion = '14.17.1';
it('is read from profile', async () => {
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
getBuildProfileAsync.mockImplementation(async () => {
return { node: nodeVersion } as BuildProfile<Platform.ANDROID>;
});
const result = await getProfilesAsync({
easJsonAccessor,
platforms: [Platform.ANDROID],
profileName: 'custom-profile',
type: 'build',
projectDir,
});
expect(result[0].profile.node).toBe(nodeVersion);
});
describe('with .nvmrc', () => {
beforeEach(async () => {
vol.reset();
await fs.mkdirp(os.tmpdir());
vol.fromJSON({ '.nvmrc': nodeVersion }, projectDir);
});
it('is read from .nvmrc', async () => {
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
getBuildProfileAsync.mockImplementation(async () => {
return {} as BuildProfile<Platform.ANDROID>;
});
const result = await getProfilesAsync({
easJsonAccessor,
platforms: [Platform.ANDROID],
profileName: 'custom-profile',
type: 'build',
projectDir,
});
expect(result[0].profile.node).toBe(nodeVersion);
});
});
});
it('throws validation error if eas.json is invalid', async () => {
getBuildProfileAsync.mockImplementation(() => {
throw new errors.InvalidEasJsonError('eas.json is not valid');
@@ -109,6 +163,7 @@ describe(getProfilesAsync, () => {
platforms: [Platform.ANDROID, Platform.IOS],
profileName: undefined,
type: 'build',
projectDir,
})
).rejects.toThrowError(/eas.json is not valid/);
});
+19
View File
@@ -56,7 +56,26 @@ export function shouldUseVersionedExpoCLIExpensive(
return !!resolveFrom.silent(projectDir, '@expo/cli');
}
/**
* Determine if we can and should use `expo export` with multiple `--platform` flags.
* This is an issue related to `expo export --all` causing issues when users have Metro web configured.
* See: https://github.com/expo/expo/pull/23621
*/
export function shouldUseVersionedExpoCLIWithExplicitPlatformsExpensive(
projectDir: string
): boolean {
const expoCliPath = resolveFrom.silent(projectDir, '@expo/cli/package.json');
if (!expoCliPath) {
return false;
}
return gteSdkVersion(require(expoCliPath).version, '0.10.11');
}
export const shouldUseVersionedExpoCLI = memoize(shouldUseVersionedExpoCLIExpensive);
export const shouldUseVersionedExpoCLIWithExplicitPlatforms = memoize(
shouldUseVersionedExpoCLIWithExplicitPlatformsExpensive
);
export async function expoCommandAsync(
projectDir: string,
+47 -2
View File
@@ -6,6 +6,9 @@ import {
ProfileType,
SubmitProfile,
} from '@expo/eas-json';
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
import Log, { learnMore } from '../log';
@@ -24,19 +27,23 @@ export async function getProfilesAsync<T extends ProfileType>({
platforms,
profileName,
type,
projectDir,
}: {
easJsonAccessor: EasJsonAccessor;
platforms: Platform[];
profileName?: string;
projectDir: string;
type: T;
}): Promise<ProfileData<T>[]> {
const results = platforms.map(async function (platform) {
const profile = await readProfileAsync({
const profile = await readProfileWithOverridesAsync({
easJsonAccessor,
platform,
type,
profileName,
projectDir,
});
return {
profile,
profileName: profileName ?? 'production',
@@ -47,16 +54,53 @@ export async function getProfilesAsync<T extends ProfileType>({
return await Promise.all(results);
}
async function readProfileAsync<T extends ProfileType>({
async function maybeSetNodeVersionFromFileAsync(
projectDir: string,
profile: BuildProfile<Platform>
): Promise<void> {
if (profile?.node) {
return;
}
const nodeVersion = await getNodeVersionFromFileAsync(projectDir);
if (nodeVersion) {
Log.log(
`The EAS build profile does not specify a Node.js version. Using the version specified in .nvmrc: ${nodeVersion} `
);
profile.node = nodeVersion;
}
}
async function getNodeVersionFromFileAsync(projectDir: string): Promise<string | undefined> {
const nvmrcPath = path.join(projectDir, '.nvmrc');
if (!(await fs.pathExists(nvmrcPath))) {
return;
}
let nodeVersion: string;
try {
nodeVersion = (await fs.readFile(nvmrcPath, 'utf8')).toString().trim();
} catch {
return undefined;
}
if (!semver.valid(semver.coerce(nodeVersion))) {
throw new Error(`Invalid node version in .nvmrc: ${nodeVersion}`);
}
return nodeVersion;
}
async function readProfileWithOverridesAsync<T extends ProfileType>({
easJsonAccessor,
platform,
type,
profileName,
projectDir,
}: {
easJsonAccessor: EasJsonAccessor;
platform: Platform;
type: T;
profileName?: string;
projectDir: string;
}): Promise<EasProfile<T>> {
if (type === 'build') {
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
@@ -66,6 +110,7 @@ async function readProfileAsync<T extends ProfileType>({
);
await maybePrintBuildProfileDeprecationWarningsAsync(easJsonAccessor, platform, profileName);
await maybeSetNodeVersionFromFileAsync(projectDir, buildProfile);
return buildProfile as EasProfile<T>;
} else {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with eas.json",
"version": "4.0.0",
"version": "5.0.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.18.6",
"@expo/eas-build-job": "1.0.30",
"@expo/eas-build-job": "1.0.39",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "10.1.0",
+14 -2
View File
@@ -91,6 +91,12 @@
"type": "object",
"description": "The build profile configuration.",
"properties": {
"withoutCredentials": {
"type": "boolean",
"description": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when working with custom builds.",
"markdownDescription": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when working with custom builds.",
"default": false
},
"extends": {
"type": "string",
"description": "The name of the build profile that the current one should inherit values from. This value can't be specified per platform."
@@ -208,8 +214,8 @@
"properties": {
"withoutCredentials": {
"type": "boolean",
"description": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when you want to build debug binaries and the debug keystore is checked in to the repository.",
"markdownDescription": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when you want to build debug binaries and the debug keystore is checked in to the repository.",
"description": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when you want to build debug binaries and the debug keystore is checked in to the repository or working with custom builds.",
"markdownDescription": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when you want to build debug binaries and the debug keystore is checked in to the repository or working with custom builds.",
"default": false
},
"image": {
@@ -301,6 +307,12 @@
"type": "object",
"description": "The iOS-specific build profile configuration.",
"properties": {
"withoutCredentials": {
"type": "boolean",
"description": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when working with custom builds.",
"markdownDescription": "When set to `true`, EAS CLI won't require you to configure credentials when building the app. This comes in handy when working with custom builds.",
"default": false
},
"simulator": {
"type": "boolean",
"description": "If set to true, creates build for simulator.",
@@ -100,6 +100,105 @@ test('valid eas.json for development client builds', async () => {
});
});
test('valid eas.json with top level withoutCredentials property', async () => {
await fs.writeJson('/project/eas.json', {
build: {
production: {},
debug: {
developmentClient: true,
withoutCredentials: true,
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const iosProfile = await EasJsonUtils.getBuildProfileAsync(accessor, Platform.IOS, 'debug');
const androidProfile = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'debug'
);
expect(androidProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
withoutCredentials: true,
});
expect(iosProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
withoutCredentials: true,
});
});
test('valid eas.json with iOS withoutCredentials property', async () => {
await fs.writeJson('/project/eas.json', {
build: {
production: {},
debug: {
developmentClient: true,
ios: { withoutCredentials: true },
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const iosProfile = await EasJsonUtils.getBuildProfileAsync(accessor, Platform.IOS, 'debug');
const androidProfile = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'debug'
);
expect(androidProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
});
expect(iosProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
withoutCredentials: true,
});
});
test('valid eas.json with top level and platform-sepcific withoutCredentials property', async () => {
await fs.writeJson('/project/eas.json', {
build: {
production: {},
debug: {
developmentClient: true,
withoutCredentials: true,
ios: { withoutCredentials: false },
},
},
});
const accessor = EasJsonAccessor.fromProjectPath('/project');
const iosProfile = await EasJsonUtils.getBuildProfileAsync(accessor, Platform.IOS, 'debug');
const androidProfile = await EasJsonUtils.getBuildProfileAsync(
accessor,
Platform.ANDROID,
'debug'
);
expect(androidProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
withoutCredentials: true,
});
expect(iosProfile).toEqual({
credentialsSource: 'remote',
distribution: 'store',
developmentClient: true,
withoutCredentials: false,
});
});
test(`valid eas.json with autoIncrement flag at build profile root`, async () => {
await fs.writeJson('/project/eas.json', {
build: {
+7 -5
View File
@@ -25,9 +25,11 @@ const CacheSchema = Joi.object({
cacheDefaultPaths: Joi.boolean(),
customPaths: Joi.array().items(Joi.string()),
paths: Joi.array().items(Joi.string()),
}).rename('customPaths', 'paths')
})
.rename('customPaths', 'paths')
.messages({
'object.rename.override': 'Cannot provide both "cache.customPaths" and "cache.paths" - use "cache.paths"'
'object.rename.override':
'Cannot provide both "cache.customPaths" and "cache.paths" - use "cache.paths"',
});
const CommonBuildProfileSchema = Joi.object({
@@ -63,6 +65,9 @@ const CommonBuildProfileSchema = Joi.object({
// custom build configuration
config: Joi.string(),
// credentials
withoutCredentials: Joi.boolean(),
});
const PlatformBuildProfileSchema = CommonBuildProfileSchema.concat(
@@ -84,9 +89,6 @@ const AndroidBuildProfileSchema = PlatformBuildProfileSchema.concat(
// build environment
ndk: Joi.string().empty(null).custom(semverCheck),
// credentials
withoutCredentials: Joi.boolean(),
// build configuration
gradleCommand: Joi.string(),
buildType: Joi.string().valid('apk', 'app-bundle'),
+3 -3
View File
@@ -70,6 +70,9 @@ export interface CommonBuildProfile {
// custom build configuration
config?: string;
// credentials
withoutCredentials?: boolean;
}
interface PlatformBuildProfile extends Omit<CommonBuildProfile, 'autoIncrement'> {
@@ -86,9 +89,6 @@ export interface AndroidBuildProfile extends PlatformBuildProfile {
image?: Android.BuilderEnvironment['image'];
ndk?: string;
// credentials
withoutCredentials?: boolean;
// build configuration
gradleCommand?: string;
buildType?: Android.BuildType.APK | Android.BuildType.APP_BUNDLE;
Generated
+8 -8
View File
@@ -1395,10 +1395,10 @@
slugify "^1.3.4"
sucrase "^3.20.0"
"@expo/[email protected]0":
version "1.0.30"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.30.tgz#f8c57a673a2cc328f4041346996c480c3d910e92"
integrity sha512-KQGn9POn5p3guMNQJ5ykG3wffRlSEpjTCLO87d5lJll9E4u1grpqHJH2chOh5ICxg7tiWcO4iXt5S8tU7Ogpig==
"@expo/[email protected]9":
version "1.0.39"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.39.tgz#d3bc6cd50c499f07255bceeb9460e092a19505f7"
integrity sha512-OqCCxLx9HRMFQDiZvfpOfNmGhsTrV15IUOhmbp9iIa+uO/VyPpBvXqiA4ENCN9Jmf6yXtirIranCeJcm+jAuSA==
dependencies:
joi "^17.9.2"
semver "^7.5.4"
@@ -1576,10 +1576,10 @@
dependencies:
cross-spawn "^7.0.3"
"@expo/[email protected]1":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.31.tgz#61e0456e54bc63bd714aa40b9be27503c032caf0"
integrity sha512-TPuaihUQ9MfVmgG5OzFdMKXcBAgv1dfLZWGpk/k5hWBjPZAoe01UXbw8GCt6F09tS2Z256d2G/kOTJzISLNcjQ==
"@expo/[email protected]4":
version "1.0.34"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.34.tgz#ba2f27a9d3e163b2d0fe6ef7775b237f7d18a286"
integrity sha512-jQoxeMWWl2mUwLGyiUD38z/FNao2jbDP8Tjvkr/YAfIOHaro36E3Vmn1YsKTYmtc41TGcFdCzqly95faRNrhvQ==
dependencies:
"@expo/logger" "1.0.30"
"@expo/spawn-async" "^1.7.2"