mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
74
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7b7eacd4e | ||
|
|
ec43906005 | ||
|
|
b71f324c83 | ||
|
|
4bb550e3bb | ||
|
|
6a55c65908 | ||
|
|
ab3495da5e | ||
|
|
63b67635e0 | ||
|
|
4b5cda3bbc | ||
|
|
63e3c1f2ab | ||
|
|
3f1543989f | ||
|
|
1c964d2ab1 | ||
|
|
bc282b6262 | ||
|
|
17661d1cf0 | ||
|
|
8d35745124 | ||
|
|
3740ee4ef9 | ||
|
|
751d3d1287 | ||
|
|
1fd14c07e1 | ||
|
|
39ea1705d8 | ||
|
|
2c96b9929d | ||
|
|
0dae24ae47 | ||
|
|
a63efe01a1 | ||
|
|
803457b37a | ||
|
|
dcb1305501 | ||
|
|
97fe2d62d2 | ||
|
|
ca0850b82b | ||
|
|
039a7b408b | ||
|
|
68660ae845 | ||
|
|
34bb2c8a75 | ||
|
|
ac5cf3d7dd | ||
|
|
8d686b855b | ||
|
|
19973bb86f | ||
|
|
d132ac09de | ||
|
|
4c5e5d4ded | ||
|
|
44af16efe1 | ||
|
|
f34487cc8e | ||
|
|
7ea1c5de17 | ||
|
|
261f611770 | ||
|
|
f9337f419a | ||
|
|
4280e10836 | ||
|
|
a2fe99c97e | ||
|
|
868fd399a4 | ||
|
|
7d60ba2cea | ||
|
|
db7461a569 | ||
|
|
facffbdcd7 | ||
|
|
dbdefa0e3d | ||
|
|
66daeade84 | ||
|
|
48ebd9bb78 | ||
|
|
3d3574d351 | ||
|
|
8da6adf4da | ||
|
|
bd2d39f26b | ||
|
|
e16e1f3871 | ||
|
|
d8bd6a1548 | ||
|
|
0f2fad3a65 | ||
|
|
78e05c48c8 | ||
|
|
fa10404843 | ||
|
|
5bf2c3a65b | ||
|
|
ec71e1168f | ||
|
|
8bf28029dc | ||
|
|
fc3c1e73a8 | ||
|
|
bb3915dc7e | ||
|
|
753df2c428 | ||
|
|
3111492f83 | ||
|
|
9c95755a46 | ||
|
|
da727ba170 | ||
|
|
4571910c4d | ||
|
|
c7cfcafff8 | ||
|
|
6f913f7320 | ||
|
|
0a49296231 | ||
|
|
7fc09b2376 | ||
|
|
fed28a87be | ||
|
|
073bdcd575 | ||
|
|
6979c8d67b | ||
|
|
0869bc511f | ||
|
|
693562d742 |
No files matched your search
+6
-12
@@ -3,24 +3,18 @@ commentConfiguration:
|
||||
epilogue: 'Generated by [CodeMention](https://github.com/expo/expo/actions/workflows/codemention.yaml)'
|
||||
rules:
|
||||
- patterns: ['**/*']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/build/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/commands/build/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/submit/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
- patterns: ['packages/eas-cli/src/commands/submit.ts']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela', 'radoslawkrzemien']
|
||||
- patterns: ['packages/eas-cli/src/update/**']
|
||||
mentions: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/update/**']
|
||||
mentions: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/schema/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-json/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
+109
@@ -8,10 +8,119 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added support to read platform version from `app.json`. ([#2778](https://github.com/expo/eas-cli/pull/2778) by [@kudo](https://github.com/kudo))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [14.2.0](https://github.com/expo/eas-cli/releases/tag/v14.2.0) - 2024-12-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `eas deploy --dry-run` flag to output tarball. ([#2761](https://github.com/expo/eas-cli/pull/2761) by [@kitten](https://github.com/kitten))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Remove random branch name generation for --auto branch name non-vcs fallback. ([#2747](https://github.com/expo/eas-cli/pull/2747) by [@wschurman](https://github.com/wschurman))
|
||||
- Upgrade @expo/multipart-body-parser. ([#2751](https://github.com/expo/eas-cli/pull/2751) by [@wschurman](https://github.com/wschurman))
|
||||
- Pass env var flag to worker deployments. ([#2763](https://github.com/expo/eas-cli/pull/2763) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Fix request for switching providers when doing Apple auth. ([#2769](https://github.com/expo/eas-cli/pull/2769) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve logging and validation in `eas env:exec` command. ([#2762](https://github.com/expo/eas-cli/pull/2762) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.1.0](https://github.com/expo/eas-cli/releases/tag/v14.1.0) - 2024-12-10
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add support for enabeling broadcast Push Notifications capability option, by setting `ios.usesBroadcastPushNotifications` to `true` in app config. ([#2748](https://github.com/expo/eas-cli/pull/2748) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add link to FYI page mentioning the workaround for Apple SMS 2FA issue. ([#2752](https://github.com/expo/eas-cli/pull/2752) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.3](https://github.com/expo/eas-cli/releases/tag/v14.0.3) - 2024-12-09
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Bump `@expo/apple-utils` to fix sending two-factor authentication codes via SMS. ([#2750](https://github.com/expo/eas-cli/pull/2750) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Change update message to allow faster copy and paste. ([#2661](https://github.com/expo/eas-cli/pull/2661) by [@jonluca](https://github.com/jonluca))
|
||||
- Allow using `$schema` field in `eas.json`. ([#2624](https://github.com/expo/eas-cli/pull/2624) by [@saiichihashimoto](https://github.com/saiichihashimoto))
|
||||
|
||||
## [14.0.2](https://github.com/expo/eas-cli/releases/tag/v14.0.2) - 2024-12-06
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Print warning instead of throwing an error when validating `CFBundleShortVersionString` against App Store requirements. ([#2741](https://github.com/expo/eas-cli/pull/2741) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.1](https://github.com/expo/eas-cli/releases/tag/v14.0.1) - 2024-12-06
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Bump `@expo/apple-utils` to improve error handling and input validation and change auth headers. ([#2745](https://github.com/expo/eas-cli/pull/2745) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [14.0.0](https://github.com/expo/eas-cli/releases/tag/v14.0.0) - 2024-12-06
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Change behavior of roll-back-to-embedded to not use current project state. ([#2722](https://github.com/expo/eas-cli/pull/2722) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add hidden fingerprint:compare feature. ([#2736](https://github.com/expo/eas-cli/pull/2736) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Update `@expo/config` and `@expo/config-plugins` to fix `eas build` command for bare iOS projects on Windows. ([#2744](https://github.com/expo/eas-cli/pull/2744) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Fix outdated gitignored Google services file warning. ([#2730](https://github.com/expo/eas-cli/pull/2730) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.2](https://github.com/expo/eas-cli/releases/tag/v13.4.2) - 2024-11-25
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade `@expo` packages to SDK 52 versions. ([#2706](https://github.com/expo/eas-cli/pull/2706) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.1](https://github.com/expo/eas-cli/releases/tag/v13.4.1) - 2024-11-22
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve logging in `eas env:pull`. ([#2720](https://github.com/expo/eas-cli/pull/2720) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.4.0](https://github.com/expo/eas-cli/releases/tag/v13.4.0) - 2024-11-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Calculate fingerprint on each update. ([#2687](https://github.com/expo/eas-cli/pull/2687) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Calculate fingerprint on each update republish. ([#2708](https://github.com/expo/eas-cli/pull/2708) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add `eas env` commands. ([#2711](https://github.com/expo/eas-cli/pull/2711) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add `--environment` flag to `eas update` command. ([#2711](https://github.com/expo/eas-cli/pull/2711) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Load readable environment variables from EAS servers on every build. ([#2644](https://github.com/expo/eas-cli/pull/2644) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Add environment field to `eas.schema.json`. ([#2719](https://github.com/expo/eas-cli/pull/2719) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Deprecate `eas secret` commands. ([#2705](https://github.com/expo/eas-cli/pull/2705) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [13.3.0](https://github.com/expo/eas-cli/releases/tag/v13.3.0) - 2024-11-18
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added `eas workflow` commands. [#2650](https://github.com/expo/eas-cli/pull/2650), [#2669](https://github.com/expo/eas-cli/pull/2669), [#2678](https://github.com/expo/eas-cli/pull/2678) by [@jonsamp](https://github.com/jonsamp) and [@sjchmiela](https://github.com/sjchmiela) ([#2683](https://github.com/expo/eas-cli/pull/2683) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
## [13.2.3](https://github.com/expo/eas-cli/releases/tag/v13.2.3) - 2024-11-15
|
||||
|
||||
## [13.2.2](https://github.com/expo/eas-cli/releases/tag/v13.2.2) - 2024-11-15
|
||||
|
||||
## [13.2.1](https://github.com/expo/eas-cli/releases/tag/v13.2.1) - 2024-11-14
|
||||
|
||||
## [13.2.0](https://github.com/expo/eas-cli/releases/tag/v13.2.0) - 2024-11-13
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "13.2.2",
|
||||
"version": "14.2.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+362
-165
@@ -94,6 +94,14 @@ eas --help COMMAND
|
||||
* [`eas device:rename`](#eas-devicerename)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas env:create [ENVIRONMENT]`](#eas-envcreate-environment)
|
||||
* [`eas env:delete [ENVIRONMENT]`](#eas-envdelete-environment)
|
||||
* [`eas env:exec ENVIRONMENT BASH_COMMAND`](#eas-envexec-environment-bash_command)
|
||||
* [`eas env:get [ENVIRONMENT]`](#eas-envget-environment)
|
||||
* [`eas env:list [ENVIRONMENT]`](#eas-envlist-environment)
|
||||
* [`eas env:pull [ENVIRONMENT]`](#eas-envpull-environment)
|
||||
* [`eas env:push [ENVIRONMENT]`](#eas-envpush-environment)
|
||||
* [`eas env:update [ENVIRONMENT]`](#eas-envupdate-environment)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas init`](#eas-init)
|
||||
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
||||
@@ -107,10 +115,6 @@ eas --help COMMAND
|
||||
* [`eas project:info`](#eas-projectinfo)
|
||||
* [`eas project:init`](#eas-projectinit)
|
||||
* [`eas project:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-projectonboarding-target_project_directory)
|
||||
* [`eas secret:create`](#eas-secretcreate)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
* [`eas secret:push`](#eas-secretpush)
|
||||
* [`eas submit`](#eas-submit)
|
||||
* [`eas update`](#eas-update)
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
@@ -127,6 +131,9 @@ eas --help COMMAND
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
* [`eas whoami`](#eas-whoami)
|
||||
* [`eas workflow:create [NAME]`](#eas-workflowcreate-name)
|
||||
* [`eas workflow:run [FILE]`](#eas-workflowrun-file)
|
||||
* [`eas workflow:validate PATH`](#eas-workflowvalidate-path)
|
||||
|
||||
## `eas account:login`
|
||||
|
||||
@@ -146,7 +153,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -163,7 +170,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -180,7 +187,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -194,7 +201,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -246,7 +253,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -267,7 +274,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -287,7 +294,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -307,7 +314,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -330,7 +337,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -367,7 +374,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -386,7 +393,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -403,7 +410,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -422,7 +429,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -457,7 +464,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -507,7 +514,7 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -536,7 +543,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -562,7 +569,7 @@ DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -612,7 +619,7 @@ DESCRIPTION
|
||||
get the latest version from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -631,7 +638,7 @@ DESCRIPTION
|
||||
update version of an app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -650,7 +657,7 @@ DESCRIPTION
|
||||
update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -667,7 +674,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -688,7 +695,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -710,7 +717,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -730,7 +737,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -752,7 +759,7 @@ DESCRIPTION
|
||||
pause a channel to stop it from sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -774,7 +781,7 @@ DESCRIPTION
|
||||
resume a channel to start sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -807,7 +814,7 @@ DESCRIPTION
|
||||
Roll a new branch out on a channel incrementally.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -830,7 +837,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -851,7 +858,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -868,7 +875,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -886,7 +893,7 @@ DESCRIPTION
|
||||
Set up credentials for building your project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -900,7 +907,7 @@ DESCRIPTION
|
||||
register new Apple Devices to use for internal distribution
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -920,7 +927,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -941,7 +948,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -962,7 +969,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -976,7 +983,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -990,7 +997,206 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
create an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:create [ENVIRONMENT] [--name <value>] [--value <value>] [--force] [--type string|file] [--visibility
|
||||
plaintext|sensitive|secret] [--scope project|account] [--environment development|preview|production]
|
||||
[--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to create the variable in. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--force Overwrite existing variable
|
||||
--name=<value> Name of the variable
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--type=(string|file) The type of variable
|
||||
--value=<value> Text value or the variable
|
||||
--visibility=(plaintext|sensitive|secret) Visibility of the variable
|
||||
|
||||
DESCRIPTION
|
||||
create an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
delete an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:delete [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--scope project|account] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable to delete. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable to delete
|
||||
--variable-name=<value> Name of the variable to delete
|
||||
|
||||
DESCRIPTION
|
||||
delete an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
execute a command with environment variables from the selected environment
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:exec ENVIRONMENT BASH_COMMAND [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to execute the command in. One of 'production', 'preview', or 'development'.
|
||||
BASH_COMMAND bash command to execute with the environment variables from the environment
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
execute a command with environment variables from the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
view an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:get [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--format long|short] [--scope project|account] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--format=(long|short) [default: short] Output format
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable
|
||||
--variable-name=<value> Name of the variable
|
||||
|
||||
DESCRIPTION
|
||||
view an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
list environment variables for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:list [ENVIRONMENT] [--include-sensitive] [--include-file-content] [--environment
|
||||
development|preview|production] [--format long|short] [--scope project|account]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to list the variables from. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--format=(long|short) [default: short] Output format
|
||||
--include-file-content Display files content in the output
|
||||
--include-sensitive Display sensitive values in the output
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
|
||||
DESCRIPTION
|
||||
list environment variables for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
pull environment variables for the selected environment to .env file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:pull [ENVIRONMENT] [--non-interactive] [--environment development|preview|production] [--path
|
||||
<value>]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to pull variables from. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production) Environment variable's environment
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--path=<value> [default: .env.local] Path to the result `.env` file
|
||||
|
||||
DESCRIPTION
|
||||
pull environment variables for the selected environment to .env file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
push environment variables from .env file to the selected environment
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:push [ENVIRONMENT] [--environment development|preview|production] [--path <value>]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to push variables to. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--path=<value> [default: .env.local] Path to the input `.env` file
|
||||
|
||||
DESCRIPTION
|
||||
push environment variables from .env file to the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
update an environment variable on the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:update [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--name <value>] [--value <value>] [--type string|file] [--visibility
|
||||
plaintext|sensitive|secret] [--scope project|account] [--environment development|preview|production]
|
||||
[--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable to update. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--name=<value> New name of the variable
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--type=(string|file) The type of variable
|
||||
--value=<value> New value or the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable to update
|
||||
--variable-name=<value> Current name of the variable
|
||||
--visibility=(plaintext|sensitive|secret) Visibility of the variable
|
||||
|
||||
DESCRIPTION
|
||||
update an environment variable on the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1098,7 +1304,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1115,7 +1321,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1132,7 +1338,7 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1162,7 +1368,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1176,7 +1382,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1199,7 +1405,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1217,82 +1423,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
create an environment secret on the current project or owner account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:create [--scope account|project] [--name <value>] [--value <value>] [--type string|file] [--force]
|
||||
[--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--force Delete and recreate existing secrets
|
||||
--name=<value> Name of the secret
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(account|project) [default: project] Scope for the secret
|
||||
--type=(string|file) The type of secret
|
||||
--value=<value> Text value or path to a file to store in the secret
|
||||
|
||||
DESCRIPTION
|
||||
create an environment secret on the current project or owner account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
delete an environment secret by ID
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:delete [--id <value>] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--id=<value> ID of the secret to delete
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
delete an environment secret by ID
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
list environment secrets available for your current app
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:list
|
||||
|
||||
DESCRIPTION
|
||||
list environment secrets available for your current app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas secret:push`
|
||||
|
||||
read environment secrets from env file and store on the server
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas secret:push [--scope account|project] [--env-file <value>] [--force] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
--env-file=<value> Env file with secrets
|
||||
--force Delete and recreate existing secrets
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(account|project) [default: project] Scope for the secrets
|
||||
|
||||
DESCRIPTION
|
||||
read environment secrets from env file and store on the server
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1323,7 +1454,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1333,35 +1464,39 @@ publish an update group
|
||||
USAGE
|
||||
$ eas update [--branch <value>] [--channel <value>] [-m <value>] [--input-dir <value>] [--skip-bundler]
|
||||
[--clear-cache] [--emit-metadata] [--rollout-percentage <value>] [-p android|ios|all] [--auto] [--private-key-path
|
||||
<value>] [--json --non-interactive]
|
||||
<value>] [--environment development|preview|production] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-m, --message=<value> A short message describing the update
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--clear-cache Clear the bundler cache before publishing
|
||||
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed information about
|
||||
the generated updates
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--rollout-percentage=<value> Percentage of users this update should be immediately available to. Users not in the
|
||||
rollout will be served the previous latest update on the branch, even if that update
|
||||
is itself being rolled out. The specified number must be an integer between 1 and
|
||||
100. When not specified, this defaults to 100.
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
-m, --message=<value> A short message describing the update
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and
|
||||
update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--clear-cache Clear the bundler cache before publishing
|
||||
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed
|
||||
information about the generated updates
|
||||
--environment=(development|preview|production) Environment to use for the server-side defined EAS environment
|
||||
variables during command execution.
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the
|
||||
certificate in expo-updates' configuration. Defaults to a file named
|
||||
"private-key.pem" in the certificate's directory. Only relevant if you
|
||||
are using code signing: https://docs.expo.dev/eas-update/code-signing/
|
||||
--rollout-percentage=<value> Percentage of users this update should be immediately available to.
|
||||
Users not in the rollout will be served the previous latest update on
|
||||
the branch, even if that update is itself being rolled out. The
|
||||
specified number must be an integer between 1 and 100. When not
|
||||
specified, this defaults to 100.
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
|
||||
DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1369,17 +1504,19 @@ configure the project to support EAS Update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:configure [-p android|ios|all] [--non-interactive]
|
||||
$ eas update:configure [-p android|ios|all] [--environment development|preview|production] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all] Platform to configure
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
-p, --platform=(android|ios|all) [default: all] Platform to configure
|
||||
--environment=(development|preview|production) Environment to use for the server-side defined EAS environment
|
||||
variables during command execution.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1400,7 +1537,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -1424,7 +1561,7 @@ DESCRIPTION
|
||||
edit all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1446,7 +1583,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1478,7 +1615,7 @@ DESCRIPTION
|
||||
roll back to an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1486,12 +1623,11 @@ roll back to the embedded update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:roll-back-to-embedded [--branch <value>] [--channel <value>] [--message <value>] [-p android|ios|all] [--auto]
|
||||
[--private-key-path <value>] [--json --non-interactive]
|
||||
$ eas update:roll-back-to-embedded [--branch <value>] [--channel <value>] [--runtime-version <value>] [--message <value>] [-p
|
||||
android|ios|all] [--private-key-path <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the rollback to embedded update group on
|
||||
--channel=<value> Channel that the published rollback to embedded update should affect
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
@@ -1501,12 +1637,13 @@ FLAGS
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--runtime-version=<value> Runtime version that the rollback to embedded update should target
|
||||
|
||||
DESCRIPTION
|
||||
roll back to the embedded update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1526,7 +1663,7 @@ DESCRIPTION
|
||||
roll back to an embedded update or an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1546,7 +1683,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1567,7 +1704,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1587,7 +1724,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1605,7 +1742,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1627,7 +1764,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1644,7 +1781,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v13.2.2/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -1660,4 +1797,64 @@ DESCRIPTION
|
||||
ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
create a new workflow configuration YAML file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:create [NAME] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the workflow file (must end with .yml or .yaml)
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
create a new workflow configuration YAML file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:run [FILE]`
|
||||
|
||||
Run an EAS workflow
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:run [FILE] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
FILE Path to the workflow file to run
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
Run an EAS workflow
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
validate a workflow configuration yaml file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:validate PATH [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
PATH Path to the workflow configuration YAML file (must end with .yml or .yaml)
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
validate a workflow configuration yaml file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+769
-258
File diff suppressed because it is too large.
Load diff
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "13.2.2",
|
||||
"version": "14.2.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "2.1.0",
|
||||
"@expo/apple-utils": "2.1.5",
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "9.0.4",
|
||||
"@expo/config-plugins": "8.0.10",
|
||||
"@expo/eas-build-job": "1.0.149",
|
||||
"@expo/eas-json": "13.2.2",
|
||||
"@expo/env": "^0.3.0",
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/eas-build-job": "1.0.156",
|
||||
"@expo/eas-json": "14.2.0",
|
||||
"@expo/env": "^0.4.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "1.0.117",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/osascript": "2.1.3",
|
||||
"@expo/package-manager": "1.5.2",
|
||||
"@expo/pkcs12": "0.1.2",
|
||||
"@expo/plist": "0.1.3",
|
||||
"@expo/multipart-body-parser": "2.0.0",
|
||||
"@expo/osascript": "2.1.4",
|
||||
"@expo/package-manager": "1.6.1",
|
||||
"@expo/pkcs12": "0.1.3",
|
||||
"@expo/plist": "0.2.0",
|
||||
"@expo/plugin-help": "5.1.23",
|
||||
"@expo/plugin-warn-if-update-available": "2.5.1",
|
||||
"@expo/prebuild-config": "7.0.9",
|
||||
"@expo/prebuild-config": "8.0.17",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "1.0.149",
|
||||
"@expo/steps": "1.0.156",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
@@ -41,6 +41,7 @@
|
||||
"chalk": "4.1.2",
|
||||
"cli-progress": "3.12.0",
|
||||
"dateformat": "4.6.3",
|
||||
"diff": "7.0.0",
|
||||
"dotenv": "16.3.1",
|
||||
"env-paths": "2.2.0",
|
||||
"envinfo": "7.11.0",
|
||||
@@ -64,7 +65,7 @@
|
||||
"mime": "3.0.0",
|
||||
"minimatch": "5.1.2",
|
||||
"minizlib": "3.0.1",
|
||||
"nanoid": "3.3.4",
|
||||
"nanoid": "3.3.8",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.1",
|
||||
"nullthrows": "1.1.1",
|
||||
@@ -96,6 +97,7 @@
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@types/cli-progress": "3.11.5",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/diff": "6.0.0",
|
||||
"@types/envinfo": "7.8.3",
|
||||
"@types/express": "4.17.15",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
@@ -177,16 +179,22 @@
|
||||
"secret": {
|
||||
"description": "manage project and account secrets"
|
||||
},
|
||||
"env": {
|
||||
"description": "manage project and account environment variables"
|
||||
},
|
||||
"update": {
|
||||
"description": "manage individual updates"
|
||||
},
|
||||
"webhook": {
|
||||
"description": "manage webhooks"
|
||||
},
|
||||
"workflow": {
|
||||
"description": "manage workflows"
|
||||
}
|
||||
},
|
||||
"warn-if-update-available": {
|
||||
"timeoutInDays": 0.5,
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
},
|
||||
"update": {
|
||||
"node": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { BranchNotFoundError } from './utils';
|
||||
@@ -8,10 +9,11 @@ import { withErrorHandlingAsync } from '../graphql/client';
|
||||
import {
|
||||
CreateUpdateBranchForAppMutation,
|
||||
CreateUpdateBranchForAppMutationVariables,
|
||||
UpdateBranch,
|
||||
UpdateBranchBasicInfoFragment,
|
||||
UpdateBranchFragment,
|
||||
} from '../graphql/generated';
|
||||
import { BranchQuery } from '../graphql/queries/BranchQuery';
|
||||
import { UpdateBranchBasicInfoFragmentNode } from '../graphql/types/UpdateBranchBasicInfo';
|
||||
import Log from '../log';
|
||||
import { formatBranch, getBranchDescription } from '../update/utils';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -126,7 +128,7 @@ function renderPageOfBranches(
|
||||
export async function createUpdateBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
): Promise<UpdateBranchBasicInfoFragment> {
|
||||
const result = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
||||
@@ -135,15 +137,17 @@ export async function createUpdateBranchOnAppAsync(
|
||||
updateBranch {
|
||||
createUpdateBranchForApp(appId: $appId, name: $name) {
|
||||
id
|
||||
name
|
||||
...UpdateBranchBasicInfoFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchBasicInfoFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -163,22 +167,21 @@ export async function ensureBranchExistsAsync(
|
||||
appId: string;
|
||||
branchName: string;
|
||||
}
|
||||
): Promise<{ branchId: string; createdBranch: boolean }> {
|
||||
): Promise<{ branch: UpdateBranchBasicInfoFragment; createdBranch: boolean }> {
|
||||
try {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
const { id } = updateBranch;
|
||||
return { branchId: id, createdBranch: false };
|
||||
return { branch: updateBranch, createdBranch: false };
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
return { branchId: newUpdateBranch.id, createdBranch: true };
|
||||
return { branch: newUpdateBranch, createdBranch: true };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
|
||||
return (
|
||||
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
|
||||
);
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
|
||||
return await vcsClient.getBranchNameAsync();
|
||||
}
|
||||
|
||||
export class BranchNotFoundError extends Error {
|
||||
|
||||
@@ -20,7 +20,11 @@ import { getNextVersionCode } from '../../project/android/versions';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
import {
|
||||
bumpAppVersionAsync,
|
||||
ensureStaticConfigExists,
|
||||
getVersionConfigTarget,
|
||||
} from '../utils/version';
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -53,9 +57,11 @@ export async function bumpVersionAsync({
|
||||
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const version = versionGetter(exp);
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
version: exp.version,
|
||||
version,
|
||||
versionCode: exp.android?.versionCode,
|
||||
});
|
||||
Log.log('Synchronized versions with build gradle');
|
||||
@@ -79,7 +85,7 @@ export async function bumpVersionInAppJsonAsync({
|
||||
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0';
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.ANDROID });
|
||||
} else {
|
||||
const versionCode = AndroidConfig.Version.getVersionCode(exp);
|
||||
const bumpedVersionCode = getNextVersionCode(versionCode);
|
||||
@@ -118,9 +124,11 @@ export async function maybeResolveVersionsAsync(
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const appVersion = versionGetter(exp);
|
||||
return {
|
||||
appBuildVersion: String(AndroidConfig.Version.getVersionCode(exp)),
|
||||
appVersion: exp.version,
|
||||
appVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -202,6 +210,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
applicationId
|
||||
);
|
||||
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
||||
let currentBuildVersion: string;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
@@ -230,7 +239,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
|
||||
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: currentBuildVersion,
|
||||
runtimeVersion:
|
||||
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
|
||||
@@ -254,7 +263,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
|
||||
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: String(nextBuildVersion),
|
||||
runtimeVersion:
|
||||
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
|
||||
|
||||
@@ -671,7 +671,7 @@ async function computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync<T extends
|
||||
}> {
|
||||
const runtimeAndFingerprintMetadata =
|
||||
await computeAndMaybeUploadFingerprintFromExpoUpdatesAsync(ctx);
|
||||
if (!runtimeAndFingerprintMetadata?.fingerprint) {
|
||||
if (!runtimeAndFingerprintMetadata?.fingerprintHash) {
|
||||
const fingerprint = await computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync(ctx);
|
||||
return {
|
||||
...runtimeAndFingerprintMetadata,
|
||||
@@ -690,10 +690,7 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
|
||||
): Promise<{
|
||||
runtimeVersion?: string;
|
||||
fingerprintSource?: FingerprintSource;
|
||||
fingerprint?: {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
};
|
||||
fingerprintHash?: string;
|
||||
}> {
|
||||
const resolvedRuntimeVersion = await resolveRuntimeVersionAsync({
|
||||
exp: ctx.exp,
|
||||
@@ -727,7 +724,7 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
|
||||
return {
|
||||
runtimeVersion: uploadedFingerprint.hash,
|
||||
fingerprintSource: uploadedFingerprint.fingerprintSource,
|
||||
fingerprint: resolvedRuntimeVersion.fingerprint,
|
||||
fingerprintHash: resolvedRuntimeVersion.fingerprintHash ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -740,7 +737,7 @@ async function computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync<T extends
|
||||
}> {
|
||||
const fingerprint = await createFingerprintAsync(ctx.projectDir, {
|
||||
workflow: ctx.workflow,
|
||||
platform: ctx.platform,
|
||||
platforms: [ctx.platform],
|
||||
env: ctx.env,
|
||||
});
|
||||
if (!fingerprint) {
|
||||
|
||||
@@ -54,9 +54,10 @@ async function resolveEnvVarsAsync({
|
||||
projectId: string;
|
||||
}): Promise<Env> {
|
||||
const environment =
|
||||
buildProfile.environment?.toUpperCase() ?? process.env.EAS_CURRENT_ENVIRONMENT;
|
||||
buildProfile.environment?.toUpperCase() ??
|
||||
resolveSuggestedEnvironmentForBuildProfileConfiguration(buildProfile);
|
||||
|
||||
if (!environment || !isEnvironment(environment)) {
|
||||
if (!isEnvironment(environment)) {
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfileName}" profile: ${
|
||||
buildProfile.env && Object.keys(buildProfile.env).length > 0
|
||||
@@ -83,24 +84,13 @@ async function resolveEnvVarsAsync({
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
|
||||
const encryptedEnvVars = environmentVariables.filter(({ name, value }) => name && !value);
|
||||
if (encryptedEnvVars.length > 0) {
|
||||
Log.warn(
|
||||
`Some environment variables defined in the "${environment.toLowerCase()}" environment on EAS servers are of "encrypted" type and cannot be read outside of the EAS servers (including EAS CLI): ${encryptedEnvVars
|
||||
.map(({ name }) => name)
|
||||
.join(
|
||||
', '
|
||||
)}. However, they will be available during the build process happening on the EAS servers. This can lead to potential configuration mismatches between the local development environment and the build environment if the encrypted environment variables are used to resolve the app config.`
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,10 +100,6 @@ async function resolveEnvVarsAsync({
|
||||
buildProfile.env && Object.keys(buildProfile.env).join(', ')
|
||||
}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables specified in the "${buildProfileName}" build profile "env" configuration.`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -145,3 +131,27 @@ async function resolveEnvVarsAsync({
|
||||
return { ...buildProfile.env };
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSuggestedEnvironmentForBuildProfileConfiguration(
|
||||
buildProfile: BuildProfile
|
||||
): EnvironmentVariableEnvironment {
|
||||
const setEnvironmentMessage =
|
||||
'Set the "environment" field in the build profile if you want to specify the environment manually.';
|
||||
if (buildProfile.distribution === 'store') {
|
||||
Log.log(
|
||||
`We detected that you are building for the "store" distribution. Resolving the environment for environment variables used during the build to "production". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Production;
|
||||
} else {
|
||||
if (buildProfile.developmentClient) {
|
||||
Log.log(
|
||||
`We detected that you are building the development client. Resolving the environment for environment variables used during the build to "development". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Development;
|
||||
}
|
||||
Log.log(
|
||||
`We detected that you are building for the "internal" distribution. Resolving the environment for environment variables used during the build to "preview". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Preview;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { learnMore } from '../../../log';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import {
|
||||
@@ -301,27 +300,6 @@ describe(readShortVersionAsync, () => {
|
||||
);
|
||||
expect(buildNumber).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('fails when build number is invalid', async () => {
|
||||
const exp = initBareWorkflowProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
|
||||
await expect(
|
||||
readShortVersionAsync(
|
||||
'/app',
|
||||
exp,
|
||||
{
|
||||
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
|
||||
},
|
||||
vcsClient
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: 0.0.7.1.028. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('managed project', () => {
|
||||
|
||||
@@ -21,7 +21,11 @@ import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
import {
|
||||
bumpAppVersionAsync,
|
||||
ensureStaticConfigExists,
|
||||
getVersionConfigTarget,
|
||||
} from '../utils/version';
|
||||
|
||||
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
|
||||
|
||||
@@ -48,9 +52,11 @@ export async function bumpVersionAsync({
|
||||
ensureStaticConfigExists(projectDir);
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
|
||||
const version = versionGetter(exp);
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
version: exp.version,
|
||||
version,
|
||||
buildNumber: exp.ios?.buildNumber,
|
||||
targets,
|
||||
});
|
||||
@@ -73,7 +79,7 @@ export async function bumpVersionInAppJsonAsync({
|
||||
Log.addNewLineIfNone();
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = IOSConfig.Version.getVersion(exp);
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
|
||||
} else {
|
||||
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
|
||||
if (isValidBuildNumber(buildNumber)) {
|
||||
@@ -109,14 +115,14 @@ function validateShortVersion({
|
||||
}): void {
|
||||
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
throw new Error(
|
||||
`The required format for "version" field from app.json/app.config.ts is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the required format. ${learnMore(
|
||||
Log.warn(
|
||||
`The required format for "version" field from app.json/app.config.ts for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
|
||||
Log.warn(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
@@ -141,8 +147,10 @@ export async function readShortVersionAsync(
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
} else {
|
||||
validateShortVersion({ shortVersion: exp.version, workflow });
|
||||
return exp.version;
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
|
||||
const shortVersion = versionGetter(exp);
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.144';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.152';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import { updateAppJsonConfigAsync } from '../appJson';
|
||||
import { bumpAppVersionAsync, getVersionConfigTarget } from '../version';
|
||||
|
||||
jest.mock('../appJson', () => ({
|
||||
__esModule: true,
|
||||
updateAppJsonConfigAsync: jest.fn().mockImplementation(
|
||||
async (
|
||||
{
|
||||
exp,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
},
|
||||
modifyConfig: (config: any) => void
|
||||
) => {
|
||||
// a mocked implementation that only mutates the config object without writing to disk
|
||||
modifyConfig(exp);
|
||||
}
|
||||
),
|
||||
}));
|
||||
|
||||
describe(bumpAppVersionAsync, () => {
|
||||
const name = 'test';
|
||||
const slug = 'test';
|
||||
const projectDir = '/app';
|
||||
const mockUpdateAppJsonConfigAsync = updateAppJsonConfigAsync as jest.MockedFunction<
|
||||
typeof updateAppJsonConfigAsync
|
||||
>;
|
||||
|
||||
it('should bump expo.version for valid semver', async () => {
|
||||
const appVersion = '1.0.0';
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: appVersion,
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('1.0.1');
|
||||
});
|
||||
|
||||
it('should bump expo.android.version if the expo.android.version exists', async () => {
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion: '1.0.0', projectDir, exp, platform: Platform.ANDROID });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('0.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.android.version).toBe('1.0.1');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.ios.version).toBe('2.0.0');
|
||||
});
|
||||
|
||||
it('should bump expo.ios.version if the expo.ios.version exists', async () => {
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion: '2.0.0', projectDir, exp, platform: Platform.IOS });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('0.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.android.version).toBe('1.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.ios.version).toBe('2.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe(getVersionConfigTarget, () => {
|
||||
const exp: ExpoConfig = {
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
it('should return the correct config target for the android platform', () => {
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.ANDROID,
|
||||
});
|
||||
expect(fieldName).toBe('expo.android.version');
|
||||
expect(versionGetter(exp)).toBe('1.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(versionUpdater(exp, '3.3.3').android.version).toBe('3.3.3');
|
||||
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
|
||||
});
|
||||
|
||||
it('should return the correct config target for the ios platform', () => {
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.IOS,
|
||||
});
|
||||
expect(fieldName).toBe('expo.ios.version');
|
||||
expect(versionGetter(exp)).toBe('2.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(versionUpdater(exp, '3.3.3').ios.version).toBe('3.3.3');
|
||||
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
|
||||
});
|
||||
|
||||
it('should return the correct config target for common version', () => {
|
||||
const exp: ExpoConfig = {
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
version: '0.0.0',
|
||||
};
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.IOS,
|
||||
});
|
||||
expect(fieldName).toBe('expo.version');
|
||||
expect(versionGetter(exp)).toBe('0.0.0');
|
||||
expect(versionUpdater(exp, '3.3.3').version).toBe('3.3.3');
|
||||
});
|
||||
});
|
||||
@@ -63,3 +63,12 @@ export function getWorkflowRunUrl(
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
|
||||
export function getProjectGitHubSettingsUrl(accountName: string, projectName: string): string {
|
||||
return new URL(
|
||||
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
|
||||
projectName
|
||||
)}/github`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
@@ -18,21 +19,25 @@ export async function bumpAppVersionAsync({
|
||||
appVersion,
|
||||
projectDir,
|
||||
exp,
|
||||
platform,
|
||||
}: {
|
||||
appVersion: string;
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
platform: Platform;
|
||||
}): Promise<void> {
|
||||
const { fieldName, versionUpdater } = getVersionConfigTarget({ exp, platform });
|
||||
|
||||
let bumpedAppVersion: string;
|
||||
if (semver.valid(appVersion)) {
|
||||
bumpedAppVersion = nullthrows(semver.inc(appVersion, 'patch'));
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold(
|
||||
`Bumping ${chalk.bold(fieldName)} from ${chalk.bold(appVersion)} to ${chalk.bold(
|
||||
bumpedAppVersion
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`);
|
||||
Log.log(`${chalk.bold(fieldName)} = ${chalk.bold(appVersion)} is not a valid semver`);
|
||||
bumpedAppVersion = (
|
||||
await promptAsync({
|
||||
type: 'text',
|
||||
@@ -42,6 +47,56 @@ export async function bumpAppVersionAsync({
|
||||
).bumpedAppVersion;
|
||||
}
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.version = bumpedAppVersion;
|
||||
versionUpdater(config, bumpedAppVersion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target version field from ExpoConfig based on the platform.
|
||||
*/
|
||||
export function getVersionConfigTarget({
|
||||
exp,
|
||||
platform,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
platform: Platform;
|
||||
}): {
|
||||
fieldName: string;
|
||||
versionGetter: (config: ExpoConfig) => string | undefined;
|
||||
versionUpdater: (config: ExpoConfig, version: string) => ExpoConfig;
|
||||
} {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
if (platform === Platform.ANDROID && typeof exp.android?.version === 'string') {
|
||||
return {
|
||||
fieldName: 'expo.android.version',
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
versionGetter: config => config.android?.version,
|
||||
versionUpdater: (config, version) => {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
config.android = { ...config.android, version };
|
||||
return config;
|
||||
},
|
||||
};
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
} else if (platform === Platform.IOS && typeof exp.ios?.version === 'string') {
|
||||
return {
|
||||
fieldName: 'expo.ios.version',
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
versionGetter: config => config.ios?.version,
|
||||
versionUpdater: (config, version) => {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
config.ios = { ...config.ios, version };
|
||||
return config;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fieldName: 'expo.version',
|
||||
versionGetter: config => config.version,
|
||||
versionUpdater: (config, version) => {
|
||||
config.version = version;
|
||||
return config;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -27,24 +27,28 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const googleServicesEnvVar =
|
||||
ctx.platform === Platform.ANDROID
|
||||
? ctx.env.GOOGLE_SERVICES_JSON
|
||||
: ctx.env.GOOGLE_SERVICES_INFO_PLIST;
|
||||
const rootDir = path.normalize(await ctx.vcsClient.getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(
|
||||
path.relative(rootDir, absGoogleServicesFilePath)
|
||||
))) &&
|
||||
!googleServicesEnvVar
|
||||
) {
|
||||
Log.warn(
|
||||
`File specified via "${ctx.platform}.googleServicesFile" field in your app.json is not checked in to your repository and won't be uploaded to the builder.`
|
||||
);
|
||||
Log.warn(
|
||||
`Use EAS Secret to pass all values that you don't want to include in your version control. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/variables/#using-secrets-in-environment-variables'
|
||||
`Use EAS file environment variables with secret or sensitive visibility to pass all values that you don't want to include in your version control to build process. ${learnMore(
|
||||
'https://docs.expo.dev/eas/environment-variables/#file-environment-variables'
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
`If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting your build profile's env.GOOGLE_SERVICES_FILE in eas.json to any non-empty string.`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/BranchQuery';
|
||||
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
||||
import Log from '../log';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
|
||||
import formatFields from '../utils/formatFields';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -303,7 +303,6 @@ export async function doesChannelExistAsync(
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a channel and links it to a branch with the same name.
|
||||
*
|
||||
* @param appId the app ID, also known as the project ID
|
||||
@@ -319,7 +318,7 @@ export async function createAndLinkChannelAsync(
|
||||
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
|
||||
): Promise<ChannelBasicInfo> {
|
||||
let branchId: string;
|
||||
let branchMessage: string;
|
||||
let hasCreatedBranch = false;
|
||||
|
||||
try {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
@@ -327,7 +326,6 @@ export async function createAndLinkChannelAsync(
|
||||
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, {
|
||||
@@ -335,7 +333,7 @@ export async function createAndLinkChannelAsync(
|
||||
name: channelName,
|
||||
});
|
||||
branchId = newBranch.id;
|
||||
branchMessage = `We also went ahead and made a branch with the same name`;
|
||||
hasCreatedBranch = true;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@@ -358,26 +356,25 @@ export async function createAndLinkChannelAsync(
|
||||
if (shouldPrintJson) {
|
||||
printJsonOnlyOutput(newChannel);
|
||||
} else {
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, appId);
|
||||
const learnMoreLink = learnMore('https://docs.expo.dev/eas-update/eas-cli/');
|
||||
|
||||
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 },
|
||||
])
|
||||
);
|
||||
if (hasCreatedBranch) {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} and branch ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} on ${chalk.bold(projectDisplayName)} project. ${learnMoreLink}`
|
||||
);
|
||||
} else {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} on ${chalk.bold(
|
||||
projectDisplayName
|
||||
)} project and connected it with existing ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} branch. ${learnMoreLink}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return newChannel;
|
||||
}
|
||||
@@ -38,7 +38,12 @@ export async function fetchBuildsAsync({
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string };
|
||||
filters?: {
|
||||
statuses?: BuildStatus[];
|
||||
platform?: RequestedPlatform;
|
||||
profile?: string;
|
||||
hasFingerprint?: boolean;
|
||||
};
|
||||
}): Promise<BuildFragment[]> {
|
||||
let builds: BuildFragment[];
|
||||
const queryFilters: InputMaybe<BuildFilter> = {};
|
||||
@@ -48,6 +53,9 @@ export async function fetchBuildsAsync({
|
||||
if (filters?.profile) {
|
||||
queryFilters['buildProfile'] = filters.profile;
|
||||
}
|
||||
if (filters?.hasFingerprint) {
|
||||
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
||||
}
|
||||
if (!filters?.statuses) {
|
||||
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
|
||||
+1
-1
@@ -225,7 +225,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'success',
|
||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
||||
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ async function ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||
}
|
||||
|
||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||
const maybeRepoRoot = PackageManagerUtils.resolveWorkspaceRoot(projectDir) ?? projectDir;
|
||||
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
getPrivateExpoConfigAsync,
|
||||
} from '../../../project/expoConfig';
|
||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { toAppPrivacy } from '../../../project/projectUtils';
|
||||
import SessionManager from '../../../user/SessionManager';
|
||||
import { Actor, getActorUsername } from '../../../user/User';
|
||||
|
||||
@@ -197,7 +196,6 @@ export async function validateOrSetProjectIdAsync({
|
||||
{
|
||||
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||
projectName: exp.slug,
|
||||
privacy: toAppPrivacy(exp.privacy),
|
||||
},
|
||||
{
|
||||
nonInteractive: options.nonInteractive,
|
||||
|
||||
+2
-11
@@ -42,22 +42,13 @@ export async function loadServerSideEnvironmentVariablesAsync({
|
||||
|
||||
if (Object.keys(serverEnvVars).length > 0) {
|
||||
Log.log(
|
||||
`Environment variables loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS servers: ${Object.keys(
|
||||
serverEnvVars
|
||||
).join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No readable environment variables found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
|
||||
const encryptedEnvVars = environmentVariables.filter(({ name, value }) => name && !value);
|
||||
if (encryptedEnvVars.length > 0) {
|
||||
Log.warn(
|
||||
`Some environment variables defined in the "${environment.toLowerCase()}" environment on EAS servers are of "encrypted" type and cannot be read outside of the EAS servers (including EAS CLI): ${encryptedEnvVars
|
||||
.map(({ name }) => name)
|
||||
.join(', ')}. `
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../graphql/generated';
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
|
||||
// NOTE: not exactly true, but, provided mapToLowercase and upperCaseAsync
|
||||
// are used in tandem, it saves on unnecessary typying in commands
|
||||
@@ -52,18 +52,19 @@ export const EASVariableFormatFlag = {
|
||||
};
|
||||
|
||||
export const EASVariableVisibilityFlag = {
|
||||
visibility: Flags.enum<'plaintext' | 'sensitive' | 'encrypted'>({
|
||||
visibility: Flags.enum<'plaintext' | 'sensitive' | 'secret'>({
|
||||
description: 'Visibility of the variable',
|
||||
options: ['plaintext', 'sensitive', 'encrypted'],
|
||||
options: ['plaintext', 'sensitive', 'secret'],
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableScopeFlag = {
|
||||
scope: Flags.enum<EnvironmentVariableScope>({
|
||||
export type EASEnvironmentVariableScopeFlagValue = 'project' | 'account';
|
||||
|
||||
export const EASEnvironmentVariableScopeFlag = {
|
||||
scope: Flags.enum<EASEnvironmentVariableScopeFlagValue>({
|
||||
description: 'Scope for the variable',
|
||||
options: mapToLowercase([EnvironmentVariableScope.Shared, EnvironmentVariableScope.Project]),
|
||||
parse: upperCaseAsync,
|
||||
default: EnvironmentVariableScope.Project,
|
||||
options: ['project', 'account'],
|
||||
default: 'project',
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -82,11 +83,10 @@ export const EasJsonOnlyFlag = {
|
||||
export const EasUpdateEnvironmentFlag = {
|
||||
environment: Flags.enum<EnvironmentVariableEnvironment | null>({
|
||||
description:
|
||||
'Environment to use the server-side defined EAS environment variables for during command execution.',
|
||||
'Environment to use for the server-side defined EAS environment variables during command execution.',
|
||||
options: mapToLowercase(Object.values(EnvironmentVariableEnvironment)),
|
||||
parse: upperCaseAsync,
|
||||
required: false,
|
||||
hidden: true,
|
||||
default: null,
|
||||
}),
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export default class BranchCreate extends EasCommand {
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
message: 'Provide a branch name:',
|
||||
initial: await getDefaultBranchNameAsync(vcsClient),
|
||||
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { getVersionConfigTarget } from '../../../build/utils/version';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -111,6 +112,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
: `What version would you like to initialize it with?`;
|
||||
Log.log(currentStateMessage);
|
||||
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform });
|
||||
const { version } = await promptAsync({
|
||||
type: platform === Platform.ANDROID ? 'number' : 'text',
|
||||
name: 'version',
|
||||
@@ -124,7 +126,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
appId: projectId,
|
||||
platform: toAppPlatform(platform),
|
||||
applicationIdentifier,
|
||||
storeVersion: exp.version ?? '1.0.0',
|
||||
storeVersion: versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: String(version),
|
||||
runtimeVersion:
|
||||
(await getRuntimeVersionNullableAsync(projectDir, exp, platform)) ?? undefined,
|
||||
|
||||
+21
-19
@@ -19,14 +19,14 @@ import {
|
||||
promptVariableTypeAsync,
|
||||
promptVariableValueAsync,
|
||||
} from '../../../utils/prompts';
|
||||
import EnvironmentVariableCreate from '../create';
|
||||
import EnvCreate from '../create';
|
||||
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../utils/prompts');
|
||||
|
||||
describe(EnvironmentVariableCreate, () => {
|
||||
describe(EnvCreate, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const variableId = 'testId';
|
||||
@@ -74,7 +74,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
|
||||
describe('in interactive mode', () => {
|
||||
it('creates a project variable', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -83,7 +83,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--visibility',
|
||||
'encrypted',
|
||||
'secret',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -110,7 +110,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
|
||||
it('updates an existing variable in the same environment', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -119,7 +119,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--visibility',
|
||||
'encrypted',
|
||||
'secret',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -171,8 +171,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a shared variable', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('creates an account-wide variable', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -181,7 +181,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -199,6 +199,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
{
|
||||
name: 'VarName',
|
||||
value: 'VarValue',
|
||||
isGlobal: true,
|
||||
environments: [EnvironmentVariableEnvironment.Production],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
@@ -207,8 +208,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if a shared variable already exists', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('throws if an account-wide variable already exists', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -217,7 +218,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -242,8 +243,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('updates if a shared variable already exists and --force flag is set', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('updates if an account-wide variable already exists and --force flag is set', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -253,7 +254,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'production',
|
||||
'--force',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -288,8 +289,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a shared variable and links it', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('creates an account-wide variable and links it', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -300,7 +301,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'development',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
'--link',
|
||||
],
|
||||
mockConfig
|
||||
@@ -324,6 +325,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
EnvironmentVariableEnvironment.Development,
|
||||
],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
isGlobal: true,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
testAccountId
|
||||
@@ -343,7 +345,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
|
||||
it('prompts for missing arguments', async () => {
|
||||
const command = new EnvironmentVariableCreate([], mockConfig);
|
||||
const command = new EnvCreate([], mockConfig);
|
||||
|
||||
jest.mocked(promptVariableNameAsync).mockImplementation(async () => 'VarName');
|
||||
jest.mocked(promptVariableValueAsync).mockImplementation(async () => 'VarValue');
|
||||
+7
-10
@@ -8,14 +8,14 @@ import { EnvironmentVariableMutation } from '../../../graphql/mutations/Environm
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableDelete from '../delete';
|
||||
import EnvDelete from '../delete';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableDelete, () => {
|
||||
describe(EnvDelete, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -40,7 +40,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(
|
||||
const command = new EnvDelete(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -66,7 +66,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableDelete([], mockConfig);
|
||||
const command = new EnvDelete([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -82,10 +82,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvDelete(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
@@ -99,7 +96,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
|
||||
const command = new EnvDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
@@ -116,7 +113,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(false);
|
||||
|
||||
const command = new EnvironmentVariableDelete(['--non-interactive'], mockConfig);
|
||||
const command = new EnvDelete(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
+5
-5
@@ -12,14 +12,14 @@ import {
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../../utils/prompts';
|
||||
import EnvironmentVariableGet from '../get';
|
||||
import EnvGet from '../get';
|
||||
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../utils/prompts');
|
||||
|
||||
describe(EnvironmentVariableGet, () => {
|
||||
describe(EnvGet, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const mockVariables: EnvironmentVariableFragment[] = [
|
||||
@@ -42,7 +42,7 @@ describe(EnvironmentVariableGet, () => {
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
const command = new EnvironmentVariableGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
|
||||
const command = new EnvGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -68,7 +68,7 @@ describe(EnvironmentVariableGet, () => {
|
||||
const errorMessage =
|
||||
"Variable name is required. Run the command with '--variable-name VARIABLE_NAME' flag";
|
||||
|
||||
const command = new EnvironmentVariableGet([], mockConfig);
|
||||
const command = new EnvGet([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -85,7 +85,7 @@ describe(EnvironmentVariableGet, () => {
|
||||
.mockResolvedValueOnce(EnvironmentVariableEnvironment.Production);
|
||||
jest.mocked(promptVariableNameAsync).mockResolvedValueOnce('TEST_VAR_1');
|
||||
|
||||
const command = new EnvironmentVariableGet([], mockConfig);
|
||||
const command = new EnvGet([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
+13
-19
@@ -11,7 +11,7 @@ import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, selectAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableLink from '../link';
|
||||
import EnvLink from '../link';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
@@ -19,7 +19,7 @@ jest.mock('../../../prompts');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableLink, () => {
|
||||
describe(EnvLink, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -39,7 +39,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
});
|
||||
|
||||
it('links a shared variable to the current project in non-interactive mode', async () => {
|
||||
it('links an account-wide variable to the current project in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -53,7 +53,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
mockVariables[0]
|
||||
);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -75,7 +75,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('links a shared variable to the current project to a specified environment', async () => {
|
||||
it('links an account-wide variable to the current project to a specified environment', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -89,7 +89,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
mockVariables[0]
|
||||
);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -137,7 +137,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
);
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -186,7 +186,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ environments: mockVariables[0].environments });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink([], mockConfig);
|
||||
const command = new EnvLink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -207,15 +207,12 @@ describe(EnvironmentVariableLink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -239,7 +236,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink([], mockConfig);
|
||||
const command = new EnvLink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -263,15 +260,12 @@ describe(EnvironmentVariableLink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
+9
-12
@@ -13,7 +13,7 @@ import {
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import EnvironmentVariableList from '../list';
|
||||
import EnvList from '../list';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
@@ -44,7 +44,7 @@ const mockVariables: EnvironmentVariableFragment[] = [
|
||||
},
|
||||
];
|
||||
|
||||
describe(EnvironmentVariableList, () => {
|
||||
describe(EnvList, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
|
||||
@@ -56,7 +56,7 @@ describe(EnvironmentVariableList, () => {
|
||||
it('lists project environment variables successfully', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList([], mockConfig);
|
||||
const command = new EnvList([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -77,7 +77,7 @@ describe(EnvironmentVariableList, () => {
|
||||
it('lists project environment variables in specified environments', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--environment', 'production'], mockConfig);
|
||||
const command = new EnvList(['--environment', 'production'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -100,7 +100,7 @@ describe(EnvironmentVariableList, () => {
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--include-sensitive'], mockConfig);
|
||||
const command = new EnvList(['--include-sensitive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -121,10 +121,10 @@ describe(EnvironmentVariableList, () => {
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
|
||||
});
|
||||
|
||||
it('lists shared environment variables successfully', async () => {
|
||||
it('lists account-wide environment variables successfully', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.sharedAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--scope', 'shared'], mockConfig);
|
||||
const command = new EnvList(['--scope', 'account'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -142,15 +142,12 @@ describe(EnvironmentVariableList, () => {
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
|
||||
});
|
||||
|
||||
it('lists shared environment variables including sensitive values', async () => {
|
||||
it('lists account-wide environment variables including sensitive values', async () => {
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.sharedWithSensitiveAsync)
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(
|
||||
['--include-sensitive', '--scope', 'shared'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvList(['--include-sensitive', '--scope', 'account'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
+12
-18
@@ -11,7 +11,7 @@ import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, selectAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableUnlink from '../unlink';
|
||||
import EnvUnlink from '../unlink';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
@@ -19,7 +19,7 @@ jest.mock('../../../prompts');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableUnlink, () => {
|
||||
describe(EnvUnlink, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -39,7 +39,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
});
|
||||
|
||||
it('unlinks a shared variable from the current project in non-interactive mode', async () => {
|
||||
it('unlinks an account-wide variable from the current project in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -54,7 +54,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
|
||||
).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
const command = new EnvUnlink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -76,7 +76,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('unlinks a shared variable from the current project in a specified environment', async () => {
|
||||
it('unlinks an account-wide variable from the current project in a specified environment', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -91,7 +91,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
|
||||
).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
const command = new EnvUnlink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -141,7 +141,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUnlink([], mockConfig);
|
||||
const command = new EnvUnlink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -162,15 +162,12 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -194,7 +191,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUnlink([], mockConfig);
|
||||
const command = new EnvUnlink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -218,15 +215,12 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
+8
-11
@@ -12,7 +12,7 @@ import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableUpdate from '../update';
|
||||
import EnvUpdate from '../update';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
@@ -20,7 +20,7 @@ jest.mock('../../../prompts');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableUpdate, () => {
|
||||
describe(EnvUpdate, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -47,7 +47,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -81,7 +81,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('updates a shared variable by selected name in non-interactive mode', async () => {
|
||||
it('updates an account-wide variable by selected name in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -93,7 +93,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -107,7 +107,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -135,7 +135,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ name: mockVariables[0].name });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUpdate([], mockConfig);
|
||||
const command = new EnvUpdate([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -154,10 +154,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUpdate(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnvDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with [1m--variable-name VARIABLE_NAME[22m flag to fix the issue"`;
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnvironmentVariableDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with [1m--variable-name VARIABLE_NAME[22m flag to fix the issue"`;
|
||||
+44
-36
@@ -5,9 +5,10 @@ import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
@@ -34,23 +35,32 @@ import {
|
||||
} from '../../utils/prompts';
|
||||
import { isEnvironment, performForEnvironmentsAsync } from '../../utils/variableUtils';
|
||||
|
||||
type CreateFlags = {
|
||||
interface RawCreateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
link?: boolean;
|
||||
force?: boolean;
|
||||
type?: 'string' | 'file';
|
||||
visibility?: 'plaintext' | 'sensitive' | 'encrypted';
|
||||
scope?: EnvironmentVariableScope;
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableCreate extends EasCommand {
|
||||
static override description =
|
||||
'create an environment variable on the current project or owner account';
|
||||
interface CreateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
link?: boolean;
|
||||
force?: boolean;
|
||||
type?: 'string' | 'file';
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
scope: EnvironmentVariableScope;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvCreate extends EasCommand {
|
||||
static override description = 'create an environment variable for the current project or account';
|
||||
|
||||
static override args = [
|
||||
{
|
||||
@@ -69,7 +79,8 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
description: 'Text value or the variable',
|
||||
}),
|
||||
link: Flags.boolean({
|
||||
description: 'Link shared variable to the current project',
|
||||
description: 'Link account-wide variable to the current project',
|
||||
hidden: true, // every account-wide variable is global for now so it's not user facing
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Overwrite existing variable',
|
||||
@@ -80,7 +91,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
options: ['string', 'file'],
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
@@ -92,9 +103,9 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableCreate);
|
||||
const { args, flags } = await this.parse(EnvCreate);
|
||||
|
||||
const validatedFlags = this.validateFlags(flags);
|
||||
const validatedFlags = this.sanitizeFlags(flags);
|
||||
|
||||
const {
|
||||
name,
|
||||
@@ -112,7 +123,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableCreate, {
|
||||
} = await this.getContextAsync(EnvCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -147,12 +158,12 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
await this.promptForOverwriteAsync({
|
||||
nonInteractive,
|
||||
force,
|
||||
message: `Shared variable with ${name} name already exists on this account.`,
|
||||
message: `Account-wide variable with ${name} name already exists on this account.`,
|
||||
suggestion: 'Do you want to unlink it first?',
|
||||
});
|
||||
|
||||
Log.withTick(
|
||||
`Unlinking shared variable ${chalk.bold(name)} on project ${chalk.bold(
|
||||
`Unlinking account-wide variable ${chalk.bold(name)} on project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
@@ -216,8 +227,8 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
overwrite = true;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Shared variable with ${name} name already exists on this account.\n` +
|
||||
`Use a different name or delete the existing variable on website or by using eas env:delete --name ${name} --scope shared command.`
|
||||
`Account-wide variable with ${name} name already exists on this account.\n` +
|
||||
`Use a different name or delete the existing variable on website or by using the "eas env:delete --name ${name} --scope account" command.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -239,6 +250,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
value,
|
||||
visibility,
|
||||
environments,
|
||||
isGlobal: true, // TODO: every account-wide variable is global for now so it's not user facing
|
||||
type: type ?? EnvironmentSecretType.String,
|
||||
},
|
||||
ownerAccount.id
|
||||
@@ -256,7 +268,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
|
||||
if (link) {
|
||||
Log.withTick(
|
||||
`Linking shared variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
`Linking account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
@@ -271,7 +283,9 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
});
|
||||
|
||||
Log.withTick(
|
||||
`Linked shared variable ${chalk.bold(name)} to project ${chalk.bold(projectDisplayName)}.`
|
||||
`Linked account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -390,7 +404,6 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
visibility: newVisibility,
|
||||
link: rest.link ?? false,
|
||||
force: rest.force ?? false,
|
||||
scope: rest.scope ?? EnvironmentVariableScope.Project,
|
||||
'non-interactive': nonInteractive,
|
||||
type: newType,
|
||||
fileName,
|
||||
@@ -398,24 +411,19 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
};
|
||||
}
|
||||
|
||||
private validateFlags(flags: CreateFlags & { type?: string; visibility?: string }): CreateFlags {
|
||||
if (flags.scope !== EnvironmentVariableScope.Shared && flags.link) {
|
||||
private sanitizeFlags(flags: RawCreateFlags): CreateFlags {
|
||||
if (flags.scope !== 'account' && flags.link) {
|
||||
throw new Error(
|
||||
`Unexpected argument: --link can only be used when creating shared variables`
|
||||
`Unexpected argument: --link can only be used when creating account-wide variables`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
flags.scope === EnvironmentVariableScope.Shared &&
|
||||
flags.environment &&
|
||||
!flags.link &&
|
||||
flags['non-interactive']
|
||||
) {
|
||||
throw new Error(
|
||||
'Unexpected argument: --environment in non-interactive mode can only be used with --link flag.'
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
return {
|
||||
...flags,
|
||||
scope:
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project,
|
||||
};
|
||||
}
|
||||
}
|
||||
+26
-15
@@ -4,8 +4,9 @@ import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
|
||||
@@ -15,17 +16,22 @@ import Log from '../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type DeleteFlags = {
|
||||
interface DeleteFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
scope?: EnvironmentVariableScope;
|
||||
};
|
||||
scope: EnvironmentVariableScope;
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable by name';
|
||||
interface RawDeleteFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable for the current project or account';
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
@@ -35,7 +41,7 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
...EasEnvironmentFlagParameters,
|
||||
description: 'Current environment of the variable to delete',
|
||||
}),
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
@@ -54,17 +60,17 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableDelete);
|
||||
const { args, flags } = await this.parse(EnvDelete);
|
||||
const {
|
||||
'variable-name': name,
|
||||
'variable-environment': environment,
|
||||
'non-interactive': nonInteractive,
|
||||
scope,
|
||||
} = this.validateInputs(flags, args);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableDelete, {
|
||||
} = await this.getContextAsync(EnvDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -139,8 +145,8 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
Log.withTick(`️Deleted variable ${selectedVariable.name}".`);
|
||||
}
|
||||
|
||||
private validateInputs(
|
||||
flags: DeleteFlags,
|
||||
private sanitizeInputs(
|
||||
flags: RawDeleteFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): DeleteFlags {
|
||||
if (flags['non-interactive']) {
|
||||
@@ -153,6 +159,11 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const scope =
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project;
|
||||
|
||||
if (environment) {
|
||||
environment = environment.toUpperCase();
|
||||
|
||||
@@ -161,9 +172,9 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
"Invalid environment. Use one of 'production', 'preview', or 'development'."
|
||||
);
|
||||
}
|
||||
return { ...flags, 'variable-environment': environment };
|
||||
return { ...flags, 'variable-environment': environment, scope };
|
||||
}
|
||||
|
||||
return flags;
|
||||
return { ...flags, scope };
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -28,9 +28,7 @@ interface RawFlags {
|
||||
|
||||
export default class EnvExec extends EasCommand {
|
||||
static override description =
|
||||
'execute a bash command with environment variables from the selected environment';
|
||||
|
||||
static override hidden = true;
|
||||
'execute a command with environment variables from the selected environment';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -160,29 +158,20 @@ export default class EnvExec extends EasCommand {
|
||||
environment,
|
||||
});
|
||||
|
||||
const secretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !value
|
||||
);
|
||||
if (secretEnvironmentVariables.length > 0) {
|
||||
Log.warn(`The following environment variables are secret and cannot be downloaded locally:`);
|
||||
for (const { name } of secretEnvironmentVariables) {
|
||||
Log.warn(`- ${name}`);
|
||||
}
|
||||
Log.warn('Proceeding with the rest of the environment variables.');
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
const nonSecretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !!value
|
||||
);
|
||||
if (nonSecretEnvironmentVariables.length === 0) {
|
||||
throw new Error('No readable environment variables found for the selected environment.');
|
||||
}
|
||||
Log.log(
|
||||
`Loaded environment variables for the selected environment "${environment.toLowerCase()}":`
|
||||
);
|
||||
for (const { name } of nonSecretEnvironmentVariables) {
|
||||
Log.log(`- ${name}`);
|
||||
|
||||
if (nonSecretEnvironmentVariables.length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${nonSecretEnvironmentVariables
|
||||
.map(e => e.name)
|
||||
.join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
|
||||
+26
-14
@@ -4,9 +4,10 @@ import chalk from 'chalk';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableFormatFlag,
|
||||
EASVariableScopeFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
@@ -20,18 +21,24 @@ import Log from '../../log';
|
||||
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../utils/prompts';
|
||||
import { formatVariable, formatVariableValue, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type GetFlags = {
|
||||
interface RawGetFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
format?: string;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
}
|
||||
|
||||
interface GetFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
format?: string;
|
||||
scope: EnvironmentVariableScope;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableGet extends EasCommand {
|
||||
static override description = 'get environment variable';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvGet extends EasCommand {
|
||||
static override description = 'view an environment variable for the current project or account';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -56,12 +63,12 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
description: 'Current environment of the variable',
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableGet);
|
||||
const { args, flags } = await this.parse(EnvGet);
|
||||
|
||||
let {
|
||||
'variable-environment': environment,
|
||||
@@ -69,12 +76,12 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
'non-interactive': nonInteractive,
|
||||
format,
|
||||
scope,
|
||||
} = this.validateInputs(flags, args);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableGet, {
|
||||
} = await this.getContextAsync(EnvGet, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -126,7 +133,7 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private validateInputs(flags: GetFlags, { environment }: { environment?: string }): GetFlags {
|
||||
private sanitizeInputs(flags: RawGetFlags, { environment }: { environment?: string }): GetFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error('Variable name is required. Run the command with --variable-name flag.');
|
||||
@@ -144,6 +151,10 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
);
|
||||
}
|
||||
|
||||
const scope =
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project;
|
||||
if (environment) {
|
||||
environment = environment.toUpperCase();
|
||||
if (!isEnvironment(environment)) {
|
||||
@@ -152,11 +163,12 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
);
|
||||
}
|
||||
return {
|
||||
'variable-environment': environment,
|
||||
...flags,
|
||||
'variable-environment': environment,
|
||||
scope,
|
||||
};
|
||||
}
|
||||
return flags;
|
||||
return { ...flags, scope };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-10
@@ -23,9 +23,10 @@ type LinkFlags = {
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableLink extends EasCommand {
|
||||
static override description = 'link a shared environment variable to the current project';
|
||||
export default class EnvLink extends EasCommand {
|
||||
static override description = 'link an account-wide environment variable to the current project';
|
||||
|
||||
// for now we only roll out global account-wide env variables so this should stay hidden
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
@@ -55,7 +56,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableLink);
|
||||
const { args, flags } = await this.parse(EnvLink);
|
||||
|
||||
let {
|
||||
'variable-name': name,
|
||||
@@ -67,7 +68,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableLink, {
|
||||
} = await this.getContextAsync(EnvLink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -87,7 +88,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
);
|
||||
}
|
||||
selectedVariable = await selectAsync(
|
||||
'Select shared variable',
|
||||
'Select account-wide variable',
|
||||
variables.map(variable => ({
|
||||
title: formatVariableName(variable),
|
||||
value: variable,
|
||||
@@ -96,7 +97,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
}
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
throw new Error(`Account-wide variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
let explicitSelect = false;
|
||||
@@ -172,7 +173,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
private validateInputs(flags: LinkFlags, { environment }: Record<string, string>): LinkFlags {
|
||||
private validateInputs(flags: LinkFlags, { environment }: { environment?: string }): LinkFlags {
|
||||
environment = environment?.toUpperCase();
|
||||
|
||||
if (environment && !isEnvironment(environment)) {
|
||||
@@ -196,9 +197,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
}
|
||||
|
||||
return {
|
||||
'variable-name': flags['variable-name'],
|
||||
'variable-environment': flags['variable-environment'],
|
||||
'non-interactive': flags['non-interactive'],
|
||||
...flags,
|
||||
environment: environments,
|
||||
};
|
||||
}
|
||||
|
||||
+28
-16
@@ -4,9 +4,10 @@ import chalk from 'chalk';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASVariableFormatFlag,
|
||||
EASVariableScopeFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import {
|
||||
@@ -66,19 +67,26 @@ async function getVariablesForScopeAsync(
|
||||
});
|
||||
}
|
||||
|
||||
type ListFlags = {
|
||||
scope: EnvironmentVariableScope;
|
||||
interface RawListFlags {
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
format: string;
|
||||
environment: EnvironmentVariableEnvironment[] | undefined;
|
||||
'include-sensitive': boolean;
|
||||
'include-file-content': boolean;
|
||||
'non-interactive'?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentValueList extends EasCommand {
|
||||
static override description = 'list environment variables for the current project';
|
||||
interface ListFlags {
|
||||
scope: EnvironmentVariableScope;
|
||||
format: string;
|
||||
environment: EnvironmentVariableEnvironment[] | undefined;
|
||||
'include-sensitive': boolean;
|
||||
'include-file-content': boolean;
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvList extends EasCommand {
|
||||
static override description = 'list environment variables for the current project or account';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -96,7 +104,7 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
}),
|
||||
...EASMultiEnvironmentFlag,
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
};
|
||||
|
||||
static override args = [
|
||||
@@ -109,7 +117,7 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentValueList);
|
||||
const { args, flags } = await this.parse(EnvList);
|
||||
|
||||
let {
|
||||
format,
|
||||
@@ -118,12 +126,12 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
'include-sensitive': includeSensitive,
|
||||
'include-file-content': includeFileContent,
|
||||
'non-interactive': nonInteractive,
|
||||
} = this.validateInputs(flags, args);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentValueList, {
|
||||
} = await this.getContextAsync(EnvList, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
@@ -156,7 +164,7 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
}
|
||||
} else {
|
||||
if (scope === EnvironmentVariableScope.Shared) {
|
||||
Log.log(chalk.bold('Shared variables for this account:'));
|
||||
Log.log(chalk.bold('Account-wide variables for this account:'));
|
||||
} else {
|
||||
Log.log(chalk.bold(`Variables for this project:`));
|
||||
}
|
||||
@@ -167,10 +175,10 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
});
|
||||
}
|
||||
|
||||
private validateInputs(
|
||||
flags: ListFlags,
|
||||
{ environment }: Record<string, string>
|
||||
): ListFlags & { 'non-interactive': boolean } {
|
||||
private sanitizeInputs(
|
||||
flags: RawListFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): ListFlags {
|
||||
if (environment && !isEnvironment(environment.toUpperCase())) {
|
||||
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
|
||||
}
|
||||
@@ -185,6 +193,10 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
...flags,
|
||||
'non-interactive': flags['non-interactive'] ?? false,
|
||||
environment: environments,
|
||||
scope:
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project,
|
||||
};
|
||||
}
|
||||
}
|
||||
+12
-13
@@ -15,10 +15,9 @@ import { confirmAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
import { isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
export default class EnvironmentVariablePull extends EasCommand {
|
||||
static override description = 'pull env file';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvPull extends EasCommand {
|
||||
static override description =
|
||||
'pull environment variables for the selected environment to .env file';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -48,7 +47,7 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
let {
|
||||
args: { environment: argEnvironment },
|
||||
flags: { environment: flagEnvironment, path: targetPath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentVariablePull);
|
||||
} = await this.parse(EnvPull);
|
||||
|
||||
let environment = flagEnvironment?.toUpperCase() ?? argEnvironment?.toUpperCase();
|
||||
|
||||
@@ -64,7 +63,7 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
projectDir,
|
||||
} = await this.getContextAsync(EnvironmentVariablePull, {
|
||||
} = await this.getContextAsync(EnvPull, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -117,7 +116,7 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
return `${variable.name}=${currentEnvLocal[variable.name]}`;
|
||||
}
|
||||
skippedSecretVariables.push(variable.name);
|
||||
return `# ${variable.name}=***** (secret variables are not available for reading)`;
|
||||
return `# ${variable.name}=***** (secret)`;
|
||||
}
|
||||
if (variable.type === EnvironmentSecretType.FileBase64 && variable.valueWithFileContent) {
|
||||
const filePath = path.join(envDir, variable.name);
|
||||
@@ -131,20 +130,20 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
await fs.writeFile(targetPath, filePrefix + envFileContentLines.join('\n'));
|
||||
|
||||
Log.log(
|
||||
`Pulled environment variables from ${environment.toLowerCase()} environment to ${targetPath}.`
|
||||
`Pulled plain text and sensitive environment variables from "${environment.toLowerCase()}" environment to ${targetPath}.`
|
||||
);
|
||||
|
||||
if (overridenSecretVariables.length > 0) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(`Reused local values for following secrets: ${overridenSecretVariables.join('\n')}`);
|
||||
Log.log(`Reused local values for following secrets: ${overridenSecretVariables.join('\n')}.`);
|
||||
}
|
||||
|
||||
if (skippedSecretVariables.length > 0) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`The following variables have the encrypted visibility and can not be read outside of EAS servers. Set their values manually in .env.local: ${skippedSecretVariables.join(
|
||||
'\n'
|
||||
)}`
|
||||
Log.log(
|
||||
`The following variables have the secret visibility and can't be read outside of EAS servers. Set their values manually in your .env file: ${skippedSecretVariables.join(
|
||||
', '
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-8
@@ -21,10 +21,9 @@ import { confirmAsync, promptAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
import { isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
export default class EnvironmentVariablePush extends EasCommand {
|
||||
static override description = 'push env file';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvPush extends EasCommand {
|
||||
static override description =
|
||||
'push environment variables from .env file to the selected environment';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -49,14 +48,14 @@ export default class EnvironmentVariablePush extends EasCommand {
|
||||
];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariablePush);
|
||||
const { args, flags } = await this.parse(EnvPush);
|
||||
|
||||
let { environment: environments, path: envPath } = this.parseFlagsAndArgs(flags, args);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariablePush, {
|
||||
} = await this.getContextAsync(EnvPush, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
@@ -106,12 +105,12 @@ export default class EnvironmentVariablePush extends EasCommand {
|
||||
const existingDifferentSharedVariablesNames = existingDifferentSharedVariables.map(
|
||||
variable => variable.name
|
||||
);
|
||||
Log.error('Shared variables cannot be overwritten by eas env:push command.');
|
||||
Log.error('Account-wide variables cannot be overwritten by eas env:push command.');
|
||||
Log.error('Remove them from the env file or unlink them from the project to continue:');
|
||||
existingDifferentSharedVariablesNames.forEach(name => {
|
||||
Log.error(`- ${name}`);
|
||||
});
|
||||
throw new Error('Shared variables cannot be overwritten by eas env:push command');
|
||||
throw new Error('Account-wide variables cannot be overwritten by eas env:push command');
|
||||
}
|
||||
|
||||
if (existingDifferentVariables.length > 0) {
|
||||
|
||||
+8
-6
@@ -18,9 +18,11 @@ type UnlinkFlags = {
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
};
|
||||
|
||||
export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
static override description = 'unlink a shared environment variable to the current project';
|
||||
export default class EnvUnlink extends EasCommand {
|
||||
static override description =
|
||||
'unlink an account-wide environment variable from the current project';
|
||||
|
||||
// for now we only roll out global account-wide env variables so this should stay hidden
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
@@ -46,7 +48,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableUnlink);
|
||||
const { args, flags } = await this.parse(EnvUnlink);
|
||||
|
||||
let {
|
||||
'variable-name': name,
|
||||
@@ -57,7 +59,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUnlink, {
|
||||
} = await this.getContextAsync(EnvUnlink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -74,7 +76,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
throw new Error("Multiple variables found, please select one using '--variable-name'");
|
||||
}
|
||||
selectedVariable = await selectAsync(
|
||||
'Select shared variable',
|
||||
'Select account-wide variable',
|
||||
variables.map(variable => ({
|
||||
title: formatVariableName(variable),
|
||||
value: variable,
|
||||
@@ -83,7 +85,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
}
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
throw new Error(`Account-wide variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
let explicitSelect = false;
|
||||
|
||||
+35
-19
@@ -6,9 +6,10 @@ import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
} from '../../commandUtils/flags';
|
||||
@@ -37,23 +38,32 @@ import {
|
||||
} from '../../utils/prompts';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type UpdateFlags = {
|
||||
interface RawUpdateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
scope?: EnvironmentVariableScope;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
visibility?: 'plaintext' | 'sensitive' | 'encrypted';
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
type?: 'string' | 'file';
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
static override description =
|
||||
'update an environment variable on the current project or owner account';
|
||||
interface UpdateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
scope: EnvironmentVariableScope;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
type?: 'string' | 'file';
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvUpdate extends EasCommand {
|
||||
static override description = 'update an environment variable on the current project or account';
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
@@ -74,7 +84,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
options: ['string', 'file'],
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
@@ -95,7 +105,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvironmentVariableUpdate);
|
||||
const { args, flags } = await this.parse(EnvUpdate);
|
||||
const {
|
||||
name,
|
||||
value: rawValue,
|
||||
@@ -106,12 +116,12 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
environment: environments,
|
||||
type,
|
||||
visibility,
|
||||
} = this.validateFlags(flags, args);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUpdate, {
|
||||
} = await this.getContextAsync(EnvUpdate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -177,6 +187,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
visibility,
|
||||
'non-interactive': nonInteractive,
|
||||
type,
|
||||
scope,
|
||||
});
|
||||
|
||||
const variable = await EnvironmentVariableMutation.updateAsync(graphqlClient, {
|
||||
@@ -194,8 +205,8 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
|
||||
Log.withTick(`Updated variable ${chalk.bold(selectedVariable.name)} ${suffix}.`);
|
||||
}
|
||||
private validateFlags(
|
||||
flags: UpdateFlags,
|
||||
private sanitizeInputs(
|
||||
flags: RawUpdateFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): UpdateFlags {
|
||||
if (flags['non-interactive']) {
|
||||
@@ -209,6 +220,11 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const scope =
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project;
|
||||
|
||||
if (environment) {
|
||||
environment = environment.toUpperCase();
|
||||
if (!isEnvironment(environment)) {
|
||||
@@ -217,12 +233,13 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
);
|
||||
}
|
||||
return {
|
||||
'variable-environment': environment,
|
||||
...flags,
|
||||
'variable-environment': environment,
|
||||
scope,
|
||||
};
|
||||
}
|
||||
|
||||
return flags;
|
||||
return { ...flags, scope };
|
||||
}
|
||||
|
||||
private async promptForMissingFlagsAsync(
|
||||
@@ -328,15 +345,14 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
name,
|
||||
value,
|
||||
environment: environments,
|
||||
visibility: newVisibility,
|
||||
scope: rest.scope ?? EnvironmentVariableScope.Project,
|
||||
'non-interactive': nonInteractive,
|
||||
type: newType,
|
||||
fileName,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { AppPlatform, BuildStatus } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { Fingerprint, FingerprintDiffItem } from '../../utils/fingerprint';
|
||||
import { createFingerprintAsync, diffFingerprint } from '../../utils/fingerprintCli';
|
||||
import { abridgedDiff } from '../../utils/fingerprintDiff';
|
||||
import formatFields, { FormatFieldsItem } from '../../utils/formatFields';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class FingerprintCompare extends EasCommand {
|
||||
static override description = 'compare fingerprints of the current project, builds and updates';
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
'build-id': Flags.string({
|
||||
aliases: ['buildId'],
|
||||
description: 'Compare the fingerprint with the build with the specified ID',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(FingerprintCompare);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive, buildId: buildIdFromArg } = flags;
|
||||
|
||||
const {
|
||||
projectId,
|
||||
privateProjectConfig: { projectDir },
|
||||
loggedIn: { graphqlClient },
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(FingerprintCompare, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
let buildId: string | null = buildIdFromArg;
|
||||
if (!buildId) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Build ID must be provided in non-interactive mode');
|
||||
}
|
||||
|
||||
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
||||
filters: { hasFingerprint: true },
|
||||
});
|
||||
if (!buildId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
||||
const buildWithFingerprint = await BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
||||
const fingerprintDebugUrl = buildWithFingerprint.fingerprint?.debugInfoUrl;
|
||||
if (!fingerprintDebugUrl) {
|
||||
Log.error('A fingerprint for the build could not be found.');
|
||||
return;
|
||||
}
|
||||
const fingerprintResponse = await fetch(fingerprintDebugUrl);
|
||||
const fingerprint = (await fingerprintResponse.json()) as Fingerprint;
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const buildPlatform = buildWithFingerprint.platform;
|
||||
const workflow = workflows[appPlatformToPlatform(buildPlatform)];
|
||||
|
||||
const projectFingerprint = await createFingerprintAsync(projectDir, {
|
||||
workflow,
|
||||
platforms: [appPlatformToString(buildPlatform)],
|
||||
debug: true,
|
||||
env: undefined,
|
||||
});
|
||||
if (!projectFingerprint) {
|
||||
Log.error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
||||
return;
|
||||
}
|
||||
if (fingerprint.hash === projectFingerprint.hash) {
|
||||
Log.log(`✅ Project fingerprint matches build`);
|
||||
return;
|
||||
} else {
|
||||
Log.log(`🔄 Project fingerprint differs from build`);
|
||||
}
|
||||
|
||||
const fingerprintDiffs = diffFingerprint(projectDir, fingerprint, projectFingerprint);
|
||||
if (!fingerprintDiffs) {
|
||||
Log.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
||||
return;
|
||||
}
|
||||
|
||||
const filePathDiffs = fingerprintDiffs.filter(diff => {
|
||||
let sourceType;
|
||||
if (diff.op === 'added') {
|
||||
sourceType = diff.addedSource.type;
|
||||
} else if (diff.op === 'removed') {
|
||||
sourceType = diff.removedSource.type;
|
||||
} else if (diff.op === 'changed') {
|
||||
sourceType = diff.beforeSource.type;
|
||||
}
|
||||
return sourceType === 'dir' || sourceType === 'file';
|
||||
});
|
||||
if (filePathDiffs.length > 0) {
|
||||
Log.newLine();
|
||||
Log.log('📁 Paths with native dependencies:');
|
||||
}
|
||||
const fields = [];
|
||||
for (const diff of filePathDiffs) {
|
||||
const field = getDiffFilePathFields(diff);
|
||||
if (!field) {
|
||||
throw new Error(`Unsupported diff: ${JSON.stringify(diff)}`);
|
||||
}
|
||||
fields.push(field);
|
||||
}
|
||||
Log.log(
|
||||
formatFields(fields, {
|
||||
labelFormat: label => ` ${chalk.dim(label)}:`,
|
||||
})
|
||||
);
|
||||
|
||||
const contentDiffs = fingerprintDiffs.filter(diff => {
|
||||
let sourceType;
|
||||
if (diff.op === 'added') {
|
||||
sourceType = diff.addedSource.type;
|
||||
} else if (diff.op === 'removed') {
|
||||
sourceType = diff.removedSource.type;
|
||||
} else if (diff.op === 'changed') {
|
||||
sourceType = diff.beforeSource.type;
|
||||
}
|
||||
return sourceType === 'contents';
|
||||
});
|
||||
|
||||
for (const diff of contentDiffs) {
|
||||
printContentDiff(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printContentDiff(diff: FingerprintDiffItem): void {
|
||||
if (diff.op === 'added') {
|
||||
const sourceType = diff.addedSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
contentsId: diff.addedSource.id,
|
||||
contentsAfter: diff.addedSource.contents,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'removed') {
|
||||
const sourceType = diff.removedSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
contentsId: diff.removedSource.id,
|
||||
contentsBefore: diff.removedSource.contents,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'changed') {
|
||||
const sourceType = diff.beforeSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
if (diff.afterSource.type !== 'contents') {
|
||||
throw new Error(
|
||||
`Changed fingerprint source types must be the same, received ${diff.beforeSource.type}, ${diff.afterSource.type}`
|
||||
);
|
||||
}
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType: diff.beforeSource.type, // before and after source types should be the same
|
||||
contentsId: diff.beforeSource.id, // before and after content ids should be the same
|
||||
contentsBefore: diff.beforeSource.contents,
|
||||
contentsAfter: diff.afterSource.contents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDiffFilePathFields(diff: FingerprintDiffItem): FormatFieldsItem | null {
|
||||
if (diff.op === 'added') {
|
||||
const sourceType = diff.addedSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
filePath: diff.addedSource.filePath,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'removed') {
|
||||
const sourceType = diff.removedSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
filePath: diff.removedSource.filePath,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'changed') {
|
||||
const sourceType = diff.beforeSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType: diff.beforeSource.type, // before and after source types should be the same
|
||||
filePath: diff.beforeSource.filePath, // before and after filePaths should be the same
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFilePathSourceFields({
|
||||
op,
|
||||
sourceType,
|
||||
filePath,
|
||||
}: {
|
||||
op: 'added' | 'removed' | 'changed';
|
||||
sourceType: 'dir' | 'file';
|
||||
filePath: string;
|
||||
}): FormatFieldsItem {
|
||||
if (sourceType === 'dir') {
|
||||
if (op === 'added') {
|
||||
return { label: 'new directory', value: filePath };
|
||||
} else if (op === 'removed') {
|
||||
return { label: 'removed directory', value: filePath };
|
||||
} else if (op === 'changed') {
|
||||
return { label: 'modified directory', value: filePath };
|
||||
}
|
||||
} else if (sourceType === 'file') {
|
||||
if (op === 'added') {
|
||||
return { label: 'new file', value: filePath };
|
||||
} else if (op === 'removed') {
|
||||
return { label: 'removed file', value: filePath };
|
||||
} else if (op === 'changed') {
|
||||
return { label: 'modified file', value: filePath };
|
||||
}
|
||||
}
|
||||
throw new Error(`Unsupported source and op: ${sourceType}, ${op}`);
|
||||
}
|
||||
|
||||
const PRETTY_CONTENT_ID: Record<string, string> = {
|
||||
'expoAutolinkingConfig:ios': 'Expo autolinking config (iOS)',
|
||||
'expoAutolinkingConfig:android': 'Expo autolinking config (Android)',
|
||||
'packageJson:scripts': 'package.json scripts',
|
||||
expoConfig: 'Expo config',
|
||||
};
|
||||
|
||||
function printContentSource({
|
||||
op,
|
||||
contentsBefore,
|
||||
contentsAfter,
|
||||
contentsId,
|
||||
}: {
|
||||
op: 'added' | 'removed' | 'changed';
|
||||
sourceType: 'contents';
|
||||
contentsBefore?: string | Buffer;
|
||||
contentsAfter?: string | Buffer;
|
||||
contentsId: string;
|
||||
}): void {
|
||||
Log.newLine();
|
||||
const prettyContentId = PRETTY_CONTENT_ID[contentsId] ?? contentsId;
|
||||
if (op === 'added') {
|
||||
Log.log(`${chalk.dim('📝 New content')}: ${prettyContentId}`);
|
||||
} else if (op === 'removed') {
|
||||
Log.log(`${chalk.dim('📝 Removed content')}: ${prettyContentId}`);
|
||||
} else if (op === 'changed') {
|
||||
Log.log(`${chalk.dim('📝 Modified content')}: ${prettyContentId}`);
|
||||
}
|
||||
printContentsDiff(contentsBefore ?? '', contentsAfter ?? '');
|
||||
}
|
||||
|
||||
function printContentsDiff(contents1: string | Buffer, contents2: string | Buffer): void {
|
||||
const stringifiedContents1 = Buffer.isBuffer(contents1) ? contents1.toString() : contents1;
|
||||
const stringifiedContents2 = Buffer.isBuffer(contents2) ? contents2.toString() : contents2;
|
||||
|
||||
const isStr1JSON = isJSON(stringifiedContents1);
|
||||
const isStr2JSON = isJSON(stringifiedContents2);
|
||||
|
||||
const prettifiedContents1 = isStr1JSON
|
||||
? JSON.stringify(JSON.parse(stringifiedContents1), null, 2)
|
||||
: stringifiedContents1;
|
||||
const prettifiedContents2 = isStr2JSON
|
||||
? JSON.stringify(JSON.parse(stringifiedContents2), null, 2)
|
||||
: stringifiedContents2;
|
||||
|
||||
abridgedDiff(prettifiedContents1, prettifiedContents2, 0);
|
||||
}
|
||||
|
||||
function isJSON(str: string): boolean {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function appPlatformToPlatform(platform: AppPlatform): Platform {
|
||||
switch (platform) {
|
||||
case AppPlatform.Android:
|
||||
return Platform.ANDROID;
|
||||
case AppPlatform.Ios:
|
||||
return Platform.IOS;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function appPlatformToString(platform: AppPlatform): string {
|
||||
switch (platform) {
|
||||
case AppPlatform.Android:
|
||||
return 'android';
|
||||
case AppPlatform.Ios:
|
||||
return 'ios';
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectBuildToCompareAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
projectDisplayName: string,
|
||||
{
|
||||
filters,
|
||||
}: {
|
||||
filters?: {
|
||||
statuses?: BuildStatus[];
|
||||
platform?: RequestedPlatform;
|
||||
profile?: string;
|
||||
hasFingerprint?: boolean;
|
||||
};
|
||||
} = {}
|
||||
): Promise<string | null> {
|
||||
const spinner = ora().start('Fetching builds…');
|
||||
|
||||
let builds;
|
||||
try {
|
||||
builds = await fetchBuildsAsync({ graphqlClient, projectId, filters });
|
||||
spinner.stop();
|
||||
} catch (error) {
|
||||
spinner.fail(
|
||||
`Something went wrong and we couldn't fetch the builds for the project ${projectDisplayName}.`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (builds.length === 0) {
|
||||
Log.warn(`No fingerprints have been computed for builds of project ${projectDisplayName}.`);
|
||||
return null;
|
||||
} else {
|
||||
const build = await selectAsync<string>(
|
||||
'Which build do you want to compare?',
|
||||
builds.map(build => ({
|
||||
title: formatBuild(build),
|
||||
value: build.id,
|
||||
}))
|
||||
);
|
||||
|
||||
return build;
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,13 @@ import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { saveProjectIdToAppConfigAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { AppPrivacy, Role } from '../../graphql/generated';
|
||||
import { Role } from '../../graphql/generated';
|
||||
import { AppMutation } from '../../graphql/mutations/AppMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import Log, { link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { createOrModifyExpoConfigAsync, getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { toAppPrivacy } from '../../project/projectUtils';
|
||||
import { Choice, confirmAsync, promptAsync } from '../../prompts';
|
||||
import { Actor } from '../../user/User';
|
||||
|
||||
@@ -337,7 +336,6 @@ export default class ProjectInit extends EasCommand {
|
||||
createdProjectId = await AppMutation.createAppAsync(graphqlClient, {
|
||||
accountId: account.id,
|
||||
projectName,
|
||||
privacy: toAppPrivacy(exp.privacy) ?? AppPrivacy.Public,
|
||||
});
|
||||
spinner.succeed(`Created ${chalk.bold(projectLink)}`);
|
||||
} catch (err) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { promptAsync, selectAsync } from '../../prompts';
|
||||
export default class EnvironmentSecretCreate extends EasCommand {
|
||||
static override description =
|
||||
'create an environment secret on the current project or owner account';
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
scope: Flags.enum({
|
||||
@@ -55,6 +56,9 @@ export default class EnvironmentSecretCreate extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('This command is deprecated. Use eas env:create instead.');
|
||||
Log.newLine();
|
||||
|
||||
let {
|
||||
flags: {
|
||||
name,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
|
||||
export default class EnvironmentSecretDelete extends EasCommand {
|
||||
static override description = 'delete an environment secret by ID';
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
id: Flags.string({
|
||||
@@ -27,6 +28,9 @@ export default class EnvironmentSecretDelete extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('This command is deprecated. Use eas env:delete instead.');
|
||||
Log.newLine();
|
||||
|
||||
let {
|
||||
flags: { id, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentSecretDelete);
|
||||
|
||||
@@ -11,6 +11,7 @@ import formatFields from '../../utils/formatFields';
|
||||
|
||||
export default class EnvironmentSecretList extends EasCommand {
|
||||
static override description = 'list environment secrets available for your current app';
|
||||
static override hidden = true;
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
@@ -18,6 +19,9 @@ export default class EnvironmentSecretList extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('This command is deprecated. Use eas env:list instead.');
|
||||
Log.newLine();
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
|
||||
@@ -24,6 +24,7 @@ import intersection from '../../utils/expodash/intersection';
|
||||
|
||||
export default class EnvironmentSecretPush extends EasCommand {
|
||||
static override description = 'read environment secrets from env file and store on the server';
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
scope: Flags.enum({
|
||||
@@ -47,6 +48,9 @@ export default class EnvironmentSecretPush extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
Log.warn('This command is deprecated. Use eas env:push instead.');
|
||||
Log.newLine();
|
||||
|
||||
const {
|
||||
flags: { scope, force, 'env-file': maybeEnvFilePath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentSecretPush);
|
||||
|
||||
@@ -88,7 +88,10 @@ describe(UpdatePublish.name, () => {
|
||||
const { platforms, runtimeVersion } = mockTestExport();
|
||||
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
@@ -111,7 +114,10 @@ describe(UpdatePublish.name, () => {
|
||||
.mocked(getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)
|
||||
.mockResolvedValue({ branchId: 'branch123', branchName: 'branchFromChannel' });
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
@@ -146,7 +152,10 @@ describe(UpdatePublish.name, () => {
|
||||
|
||||
// Mock an existing branch, so we don't create a new one
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AppJSONConfig, ExpoConfig, PackageJSONConfig, getConfig } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { vol } from 'memfs';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
@@ -82,16 +81,23 @@ describe(UpdateRollBackToEmbedded.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a roll back to embedded with --non-interactive, --branch, and --message', async () => {
|
||||
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||
it('creates a roll back to embedded with --non-interactive, --branch, --message, and --runtime-version', async () => {
|
||||
const flags = [
|
||||
'--non-interactive',
|
||||
'--branch=branch123',
|
||||
'--message=abc',
|
||||
'--runtime-version=exposdk:47.0.0',
|
||||
];
|
||||
|
||||
mockTestProject();
|
||||
const platforms = ['android', 'ios'];
|
||||
const runtimeVersion = 'exposdk:47.0.0';
|
||||
jest.mocked(Updates.getRuntimeVersionAsync).mockResolvedValue(runtimeVersion);
|
||||
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
@@ -104,19 +110,26 @@ describe(UpdateRollBackToEmbedded.name, () => {
|
||||
expect(PublishMutation.publishUpdateGroupAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a roll back to embedded with --non-interactive, --channel, and --message', async () => {
|
||||
const flags = ['--non-interactive', '--channel=channel123', '--message=abc'];
|
||||
it('creates a roll back to embedded with --non-interactive, --channel, --message, and --runtime-version', async () => {
|
||||
const flags = [
|
||||
'--non-interactive',
|
||||
'--channel=channel123',
|
||||
'--message=abc',
|
||||
'--runtime-version=exposdk:47.0.0',
|
||||
];
|
||||
|
||||
const { projectId } = mockTestProject();
|
||||
const platforms = ['android', 'ios'];
|
||||
const runtimeVersion = 'exposdk:47.0.0';
|
||||
jest.mocked(Updates.getRuntimeVersionAsync).mockResolvedValue(runtimeVersion);
|
||||
|
||||
jest
|
||||
.mocked(getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)
|
||||
.mockResolvedValue({ branchId: updateStub.branch.id, branchName: 'branchFromChannel' });
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
@@ -142,18 +155,25 @@ describe(UpdateRollBackToEmbedded.name, () => {
|
||||
});
|
||||
|
||||
it('creates a roll back to embedded with the public expo config', async () => {
|
||||
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];
|
||||
const flags = [
|
||||
'--non-interactive',
|
||||
'--branch=branch123',
|
||||
'--message=abc',
|
||||
'--runtime-version=exposdk:47.0.0',
|
||||
];
|
||||
|
||||
// Add configuration to the project that should not be included in the update
|
||||
mockTestProject();
|
||||
|
||||
const platforms = ['ios'];
|
||||
const runtimeVersion = 'exposdk:47.0.0';
|
||||
jest.mocked(Updates.getRuntimeVersionAsync).mockResolvedValue(runtimeVersion);
|
||||
|
||||
// Mock an existing branch, so we don't create a new one
|
||||
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
|
||||
branchId: 'branch123',
|
||||
branch: {
|
||||
id: 'branch123',
|
||||
name: 'wat',
|
||||
},
|
||||
createdBranch: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
import fetch from '../../fetch';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
FingerprintInfoGroup,
|
||||
PublishUpdateGroupInput,
|
||||
StatuspageServiceName,
|
||||
UpdateInfoGroup,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
getRuntimeVersionInfoObjectsAsync,
|
||||
getUpdateMessageForCommandAsync,
|
||||
isUploadedAssetCountAboveWarningThreshold,
|
||||
maybeCalculateFingerprintForRuntimeVersionInfoObjectsWithoutExpoUpdatesAsync,
|
||||
platformDisplayNames,
|
||||
resolveInputDirectoryAsync,
|
||||
uploadAssetsAsync,
|
||||
@@ -398,27 +400,38 @@ export default class UpdatePublish extends EasCommand {
|
||||
runtimeVersionInfoObjects
|
||||
);
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
});
|
||||
|
||||
const runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping = await Promise.all(
|
||||
runtimeToPlatformsAndFingerprintInfoMapping.map(async info => {
|
||||
return {
|
||||
...info,
|
||||
fingerprintSource: info.fingerprint
|
||||
? (
|
||||
await maybeUploadFingerprintAsync({
|
||||
hash: info.runtimeVersion,
|
||||
fingerprint: info.fingerprint,
|
||||
graphqlClient,
|
||||
})
|
||||
).fingerprintSource ?? null
|
||||
: null,
|
||||
};
|
||||
})
|
||||
);
|
||||
const runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMappingFromExpoUpdates =
|
||||
await Promise.all(
|
||||
runtimeToPlatformsAndFingerprintInfoMapping.map(async info => {
|
||||
return {
|
||||
...info,
|
||||
fingerprintSource: info.fingerprint
|
||||
? (
|
||||
await maybeUploadFingerprintAsync({
|
||||
hash: info.runtimeVersion,
|
||||
fingerprint: info.fingerprint,
|
||||
graphqlClient,
|
||||
})
|
||||
).fingerprintSource ?? null
|
||||
: null,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping =
|
||||
await maybeCalculateFingerprintForRuntimeVersionInfoObjectsWithoutExpoUpdatesAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping:
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMappingFromExpoUpdates,
|
||||
workflowsByPlatform: workflows,
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
const runtimeVersionToRolloutInfoGroup =
|
||||
rolloutPercentage !== undefined
|
||||
@@ -436,7 +449,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
// Sort the updates into different groups based on their platform specific runtime versions
|
||||
const updateGroups: PublishUpdateGroupInput[] =
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(
|
||||
({ runtimeVersion, platforms, fingerprintSource }) => {
|
||||
({ runtimeVersion, platforms, fingerprintSource, fingerprintInfoGroup }) => {
|
||||
const localUpdateInfoGroup = Object.fromEntries(
|
||||
platforms.map(platform => [
|
||||
platform,
|
||||
@@ -455,19 +468,33 @@ export default class UpdatePublish extends EasCommand {
|
||||
])
|
||||
)
|
||||
: null;
|
||||
const transformedFingerprintInfoGroup = Object.entries(fingerprintInfoGroup).reduce(
|
||||
(prev, [platform, fingerprintInfo]) => {
|
||||
return {
|
||||
...prev,
|
||||
[platform]: {
|
||||
...fingerprintInfo,
|
||||
fingerprintSource: transformFingerprintSource(fingerprintInfo.fingerprintSource),
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as FingerprintInfoGroup
|
||||
);
|
||||
|
||||
return {
|
||||
branchId,
|
||||
branchId: branch.id,
|
||||
updateInfoGroup: localUpdateInfoGroup,
|
||||
rolloutInfoGroup: localRolloutInfoGroup,
|
||||
runtimeFingerprintSource: fingerprintSource
|
||||
? transformFingerprintSource(fingerprintSource)
|
||||
: null,
|
||||
fingerprintInfoGroup: transformedFingerprintInfoGroup,
|
||||
runtimeVersion,
|
||||
message: updateMessage,
|
||||
gitCommitHash,
|
||||
isGitWorkingTreeDirty,
|
||||
awaitingCodeSigningInfo: !!codeSigningInfo,
|
||||
environment: environment ?? null,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Platform as PublishPlatform } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
@@ -12,10 +11,12 @@ import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
import fetch from '../../fetch';
|
||||
import {
|
||||
PublishUpdateGroupInput,
|
||||
RuntimeFragment,
|
||||
StatuspageServiceName,
|
||||
UpdatePublishMutation,
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import { RuntimeQuery } from '../../graphql/queries/RuntimeQuery';
|
||||
import Log, { link } from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
@@ -29,10 +30,8 @@ import {
|
||||
defaultPublishPlatforms,
|
||||
getBranchNameForCommandAsync,
|
||||
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects,
|
||||
getRuntimeVersionInfoObjectsAsync,
|
||||
getUpdateMessageForCommandAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
|
||||
import { getUpdateJsonInfosForUpdates } from '../../update/utils';
|
||||
import {
|
||||
@@ -45,12 +44,13 @@ import {
|
||||
import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
import { Connection, QueryParams, selectPaginatedAsync } from '../../utils/relay';
|
||||
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
|
||||
|
||||
type RawUpdateFlags = {
|
||||
auto: boolean;
|
||||
branch?: string;
|
||||
channel?: string;
|
||||
'runtime-version'?: string;
|
||||
message?: string;
|
||||
platform: string;
|
||||
'private-key-path'?: string;
|
||||
@@ -59,10 +59,10 @@ type RawUpdateFlags = {
|
||||
};
|
||||
|
||||
type UpdateFlags = {
|
||||
auto: boolean;
|
||||
platform: RequestedPlatform;
|
||||
branchName?: string;
|
||||
channelName?: string;
|
||||
runtimeVersion?: string;
|
||||
updateMessage?: string;
|
||||
privateKeyPath?: string;
|
||||
json: boolean;
|
||||
@@ -81,6 +81,10 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
description: 'Channel that the published rollback to embedded update should affect',
|
||||
required: false,
|
||||
}),
|
||||
'runtime-version': Flags.string({
|
||||
description: 'Runtime version that the rollback to embedded update should target',
|
||||
required: false,
|
||||
}),
|
||||
message: Flags.string({
|
||||
description: 'A short message describing the rollback to embedded update',
|
||||
required: false,
|
||||
@@ -95,11 +99,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
default: 'all',
|
||||
required: false,
|
||||
}),
|
||||
auto: Flags.boolean({
|
||||
description:
|
||||
'Use the current git branch and commit message for the EAS branch and update message',
|
||||
default: false,
|
||||
}),
|
||||
'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. Only relevant if you are using code signing: https://docs.expo.dev/eas-update/code-signing/`,
|
||||
required: false,
|
||||
@@ -117,10 +116,10 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
const { flags: rawFlags } = await this.parse(UpdateRollBackToEmbedded);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(rawFlags);
|
||||
const {
|
||||
auto: autoFlag,
|
||||
platform: platformFlag,
|
||||
channelName: channelNameArg,
|
||||
updateMessage: updateMessageArg,
|
||||
runtimeVersion: runtimeVersionArg,
|
||||
privateKeyPath,
|
||||
json: jsonFlag,
|
||||
nonInteractive,
|
||||
@@ -171,14 +170,14 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
projectId,
|
||||
channelNameArg,
|
||||
branchNameArg,
|
||||
autoFlag,
|
||||
autoFlag: false,
|
||||
nonInteractive,
|
||||
paginatedQueryOptions,
|
||||
});
|
||||
|
||||
const updateMessage = await getUpdateMessageForCommandAsync(vcsClient, {
|
||||
updateMessageArg,
|
||||
autoFlag,
|
||||
autoFlag: false,
|
||||
nonInteractive,
|
||||
jsonFlag,
|
||||
});
|
||||
@@ -186,28 +185,33 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
const realizedPlatforms: UpdatePublishPlatform[] =
|
||||
platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
});
|
||||
|
||||
const gitCommitHash = await vcsClient.getCommitHashAsync();
|
||||
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
|
||||
const selectedRuntime =
|
||||
runtimeVersionArg ??
|
||||
(
|
||||
await UpdateRollBackToEmbedded.selectRuntimeAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
})
|
||||
)?.version;
|
||||
if (!selectedRuntime) {
|
||||
Errors.error('Must select a runtime or provide the --runtimeVersion flag', { exit: 1 });
|
||||
}
|
||||
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const runtimeVersionInfoObjects = await getRuntimeVersionInfoObjectsAsync({
|
||||
exp,
|
||||
platforms: realizedPlatforms,
|
||||
projectDir,
|
||||
workflows: {
|
||||
...workflows,
|
||||
web: Workflow.UNKNOWN,
|
||||
},
|
||||
env: undefined,
|
||||
});
|
||||
const runtimeToPlatformsAndFingerprintInfoMapping =
|
||||
getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects(
|
||||
runtimeVersionInfoObjects
|
||||
realizedPlatforms.map(platform => ({
|
||||
platform,
|
||||
runtimeVersionInfo: {
|
||||
runtimeVersion: selectedRuntime,
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups'];
|
||||
@@ -215,10 +219,8 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
try {
|
||||
newUpdates = await this.publishRollbacksAsync({
|
||||
graphqlClient,
|
||||
isGitWorkingTreeDirty,
|
||||
gitCommitHash,
|
||||
updateMessage,
|
||||
branchId,
|
||||
branchId: branch.id,
|
||||
codeSigningInfo,
|
||||
runtimeToPlatformsAndFingerprintInfoMapping,
|
||||
realizedPlatforms,
|
||||
@@ -232,13 +234,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput(getUpdateJsonInfosForUpdates(newUpdates));
|
||||
} else {
|
||||
if (new Set(newUpdates.map(update => update.group)).size > 1) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
'👉 Since multiple runtime versions are defined, multiple update groups have been published.'
|
||||
);
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
for (const runtime of uniqBy(
|
||||
@@ -276,14 +271,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
: []),
|
||||
...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
|
||||
{ label: 'Message', value: updateMessage ?? '' },
|
||||
...(gitCommitHash
|
||||
? [
|
||||
{
|
||||
label: 'Commit',
|
||||
value: `${gitCommitHash}${isGitWorkingTreeDirty ? '*' : ''}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: 'EAS Dashboard', value: updateGroupLink },
|
||||
])
|
||||
);
|
||||
@@ -294,8 +281,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
|
||||
private async publishRollbacksAsync({
|
||||
graphqlClient,
|
||||
isGitWorkingTreeDirty,
|
||||
gitCommitHash,
|
||||
updateMessage,
|
||||
branchId,
|
||||
codeSigningInfo,
|
||||
@@ -303,8 +288,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
realizedPlatforms,
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
isGitWorkingTreeDirty: boolean | undefined;
|
||||
gitCommitHash: string | undefined;
|
||||
updateMessage: string | undefined;
|
||||
branchId: string;
|
||||
codeSigningInfo: CodeSigningInfo | undefined;
|
||||
@@ -329,8 +312,6 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
rollBackToEmbeddedInfoGroup: localRollbackInfoGroup,
|
||||
runtimeVersion,
|
||||
message: updateMessage,
|
||||
gitCommitHash,
|
||||
isGitWorkingTreeDirty,
|
||||
awaitingCodeSigningInfo: !!codeSigningInfo,
|
||||
};
|
||||
}
|
||||
@@ -381,22 +362,63 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
return newUpdates;
|
||||
}
|
||||
|
||||
private static async selectRuntimeAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
branchName,
|
||||
batchSize = 5,
|
||||
}: {
|
||||
appId: string;
|
||||
branchName: string;
|
||||
batchSize?: number;
|
||||
}
|
||||
): Promise<RuntimeFragment | null> {
|
||||
const queryAsync = async (queryParams: QueryParams): Promise<Connection<RuntimeFragment>> => {
|
||||
return await RuntimeQuery.getRuntimesOnBranchAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
first: queryParams.first,
|
||||
after: queryParams.after,
|
||||
last: queryParams.last,
|
||||
before: queryParams.before,
|
||||
});
|
||||
};
|
||||
const getTitleAsync = async (runtime: RuntimeFragment): Promise<string> => {
|
||||
return runtime.version;
|
||||
};
|
||||
return await selectPaginatedAsync({
|
||||
queryAsync,
|
||||
getTitleAsync,
|
||||
printedType: 'target runtime',
|
||||
pageSize: batchSize,
|
||||
});
|
||||
}
|
||||
|
||||
private sanitizeFlags(flags: RawUpdateFlags): UpdateFlags {
|
||||
const nonInteractive = flags['non-interactive'] ?? false;
|
||||
|
||||
const { auto, branch: branchName, channel: channelName, message: updateMessage } = flags;
|
||||
if (nonInteractive && !auto && !(updateMessage && (branchName || channelName))) {
|
||||
const {
|
||||
branch: branchName,
|
||||
channel: channelName,
|
||||
message: updateMessage,
|
||||
'runtime-version': runtimeVersion,
|
||||
} = flags;
|
||||
if (nonInteractive && !(updateMessage && (branchName || channelName))) {
|
||||
Errors.error(
|
||||
'--branch and --message, or --channel and --message are required when updating in non-interactive mode unless --auto is specified',
|
||||
'--branch and --message, or --channel and --message are required in non-interactive mode',
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
if (nonInteractive && !runtimeVersion) {
|
||||
Errors.error('--runtimeVersion is required in non-interactive mode', { exit: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
auto,
|
||||
branchName,
|
||||
channelName,
|
||||
updateMessage,
|
||||
runtimeVersion,
|
||||
platform: flags.platform as RequestedPlatform,
|
||||
privateKeyPath: flags['private-key-path'],
|
||||
nonInteractive,
|
||||
|
||||
@@ -43,16 +43,18 @@ interface DeployFlags {
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
deploymentIdentifier?: string;
|
||||
exportDir: string;
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
interface RawDeployFlags {
|
||||
'non-interactive': boolean;
|
||||
environment?: string;
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
json: boolean;
|
||||
prod: boolean;
|
||||
alias?: string;
|
||||
id?: string;
|
||||
'export-dir': string;
|
||||
'dry-run': boolean;
|
||||
}
|
||||
|
||||
interface DeployInProgressParams {
|
||||
@@ -90,10 +92,11 @@ export default class WorkerDeploy extends EasCommand {
|
||||
helpValue: 'dir',
|
||||
default: 'dist',
|
||||
}),
|
||||
environment: {
|
||||
...EASEnvironmentFlag.environment,
|
||||
description: 'Deploy with EAS Environment Variables matching the specified environment.',
|
||||
},
|
||||
'dry-run': Flags.boolean({
|
||||
description: 'Outputs a tarball of the new deployment instead of uploading it.',
|
||||
default: false,
|
||||
}),
|
||||
...EASEnvironmentFlag,
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
@@ -268,6 +271,16 @@ export default class WorkerDeploy extends EasCommand {
|
||||
})
|
||||
);
|
||||
|
||||
if (flags.dryRun) {
|
||||
const DRY_RUN_OUTPUT_PATH = 'deploy.tar.gz';
|
||||
await fs.promises.copyFile(tarPath, DRY_RUN_OUTPUT_PATH);
|
||||
progress.succeed('Saved deploy.tar.gz tarball');
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput({ tarPath: DRY_RUN_OUTPUT_PATH });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uploadUrl = await getSignedDeploymentUrlAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
deploymentIdentifier: flags.deploymentIdentifier,
|
||||
@@ -382,6 +395,8 @@ export default class WorkerDeploy extends EasCommand {
|
||||
aliasName: flags.alias?.trim().toLowerCase(),
|
||||
deploymentIdentifier: flags.id?.trim(),
|
||||
exportDir: flags['export-dir'],
|
||||
environment: flags['environment'],
|
||||
dryRun: flags['dry-run'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs/promises';
|
||||
import fsExtra from 'fs-extra';
|
||||
import path from 'path';
|
||||
import prompts from 'prompts';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import Log from '../../log';
|
||||
import { WorkflowFile } from '../../utils/workflowFile';
|
||||
|
||||
const DEFAULT_WORKFLOW_NAME = 'workflow.yml';
|
||||
const HELLO_WORLD_TEMPLATE = `name: Hello World
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
|
||||
jobs:
|
||||
hello_world:
|
||||
steps:
|
||||
- uses: eas/checkout
|
||||
- run: echo "Hello, World"
|
||||
`;
|
||||
|
||||
export class WorkflowCreate extends EasCommand {
|
||||
static override description = 'create a new workflow configuration YAML file';
|
||||
|
||||
static override args = [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name of the workflow file (must end with .yml or .yaml)',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectDir,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { name: argFileName },
|
||||
flags,
|
||||
} = await this.parse(WorkflowCreate);
|
||||
|
||||
const { projectDir } = await this.getContextAsync(WorkflowCreate, {
|
||||
nonInteractive: flags['non-interactive'],
|
||||
});
|
||||
|
||||
let fileName = argFileName;
|
||||
|
||||
if (!fileName) {
|
||||
const response = await prompts({
|
||||
type: 'text',
|
||||
name: 'fileName',
|
||||
message: 'What would you like to name your workflow file?',
|
||||
initial: DEFAULT_WORKFLOW_NAME,
|
||||
validate: value => {
|
||||
try {
|
||||
WorkflowFile.validateYamlExtension(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : 'Invalid file name';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.fileName) {
|
||||
Log.warn('Workflow creation cancelled.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
fileName = response.fileName;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensureWorkflowsDirectoryExistsAsync({ projectDir });
|
||||
await this.createWorkflowFileAsync({ fileName, projectDir });
|
||||
} catch (error) {
|
||||
Log.error('Failed to create workflow file.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureWorkflowsDirectoryExistsAsync({
|
||||
projectDir,
|
||||
}: {
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await fs.access(path.join(projectDir, '.eas', 'workflows'));
|
||||
} catch {
|
||||
await fs.mkdir(path.join(projectDir, '.eas', 'workflows'), { recursive: true });
|
||||
Log.withTick(`Created directory ${chalk.bold(path.join(projectDir, '.eas', 'workflows'))}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async createWorkflowFileAsync({
|
||||
fileName,
|
||||
projectDir,
|
||||
}: {
|
||||
fileName: string;
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
WorkflowFile.validateYamlExtension(fileName);
|
||||
|
||||
const filePath = path.join(projectDir, '.eas', 'workflows', fileName);
|
||||
|
||||
if (await fsExtra.pathExists(filePath)) {
|
||||
throw new Error(`Workflow file already exists: ${filePath}`);
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, HELLO_WORLD_TEMPLATE);
|
||||
Log.withTick(`Created ${chalk.bold(filePath)}`);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
import fs from 'node:fs';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { getWorkflowRunUrl } from '../../build/utils/url';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { WorkflowProjectSourceType } from '../../graphql/generated';
|
||||
import { WorkflowRevisionMutation } from '../../graphql/mutations/WorkflowRevisionMutation';
|
||||
import { WorkflowRunMutation } from '../../graphql/mutations/WorkflowRunMutation';
|
||||
import Log, { link } from '../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { uploadAccountScopedEasJsonAsync } from '../../project/uploadAccountScopedEasJsonAsync';
|
||||
import { uploadAccountScopedProjectSourceAsync } from '../../project/uploadAccountScopedProjectSourceAsync';
|
||||
import { WorkflowFile } from '../../utils/workflowFile';
|
||||
|
||||
export default class WorkflowRun extends EasCommand {
|
||||
static override description = 'Run an EAS workflow';
|
||||
|
||||
// TODO(@sjchmiela): Keep command hidden until workflows are live
|
||||
static override hidden = true;
|
||||
static override state = 'beta';
|
||||
|
||||
static override args = [{ name: 'file', description: 'Path to the workflow file to run' }];
|
||||
|
||||
static override flags = {
|
||||
@@ -34,8 +32,6 @@ export default class WorkflowRun extends EasCommand {
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags, args } = await this.parse(WorkflowRun);
|
||||
|
||||
Log.warn('Workflows are in beta and subject to breaking changes.');
|
||||
|
||||
const {
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
loggedIn: { graphqlClient },
|
||||
@@ -48,7 +44,12 @@ export default class WorkflowRun extends EasCommand {
|
||||
|
||||
let yamlConfig: string;
|
||||
try {
|
||||
yamlConfig = await fs.promises.readFile(path.join(projectDir, args.file), 'utf8');
|
||||
const workflowFileContents = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath: args.file,
|
||||
});
|
||||
Log.log(`Using workflow file from ${workflowFileContents.filePath}`);
|
||||
yamlConfig = workflowFileContents.yamlConfig;
|
||||
} catch (err) {
|
||||
Log.error('Failed to read workflow file.');
|
||||
|
||||
@@ -61,6 +62,23 @@ export default class WorkflowRun extends EasCommand {
|
||||
} = await getDynamicPrivateProjectConfigAsync();
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
try {
|
||||
await WorkflowRevisionMutation.validateWorkflowYamlConfigAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
yamlConfig,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof CombinedError) {
|
||||
WorkflowFile.maybePrintWorkflowFileValidationErrors({
|
||||
error,
|
||||
accountName: account.name,
|
||||
projectName,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let projectArchiveBucketKey: string;
|
||||
let easJsonBucketKey: string;
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import * as YAML from 'yaml';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { WorkflowRevisionMutation } from '../../graphql/mutations/WorkflowRevisionMutation';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { WorkflowFile } from '../../utils/workflowFile';
|
||||
|
||||
export class WorkflowValidate extends EasCommand {
|
||||
static override description = 'validate a workflow configuration yaml file';
|
||||
static override hidden = true;
|
||||
|
||||
static override args = [
|
||||
{
|
||||
@@ -18,81 +20,83 @@ export class WorkflowValidate extends EasCommand {
|
||||
},
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { path: filePath },
|
||||
flags,
|
||||
} = await this.parse(WorkflowValidate);
|
||||
|
||||
const {
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
loggedIn: { graphqlClient },
|
||||
projectDir,
|
||||
} = await this.getContextAsync(WorkflowValidate, {
|
||||
nonInteractive: flags['non-interactive'],
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const {
|
||||
projectId,
|
||||
exp: { slug: projectName },
|
||||
} = await getDynamicPrivateProjectConfigAsync();
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
const spinner = ora().start('Validating the workflow YAML file…');
|
||||
|
||||
try {
|
||||
await validateYAMLAsync(filePath);
|
||||
const workflowFileContents = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath,
|
||||
});
|
||||
Log.log(`Using workflow file from ${workflowFileContents.filePath}`);
|
||||
|
||||
const parsedYaml = YAML.parse(workflowFileContents.yamlConfig);
|
||||
|
||||
// Check if the parsed result is empty or null
|
||||
if (
|
||||
parsedYaml === null ||
|
||||
parsedYaml === undefined ||
|
||||
(typeof parsedYaml === 'object' && Object.keys(parsedYaml).length === 0)
|
||||
) {
|
||||
throw new Error('YAML file is empty or contains only comments.');
|
||||
}
|
||||
|
||||
await WorkflowRevisionMutation.validateWorkflowYamlConfigAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
yamlConfig: workflowFileContents.yamlConfig,
|
||||
});
|
||||
|
||||
spinner.succeed('Workflow configuration YAML is valid.');
|
||||
} catch (error) {
|
||||
spinner.fail('Workflow configuration YAML is not valid.');
|
||||
|
||||
if (error instanceof YAML.YAMLParseError) {
|
||||
Log.error(`YAML syntax error: ${error.message}`);
|
||||
} else if (error instanceof CombinedError) {
|
||||
WorkflowFile.maybePrintWorkflowFileValidationErrors({
|
||||
error,
|
||||
accountName: account.name,
|
||||
projectName,
|
||||
});
|
||||
|
||||
throw error;
|
||||
} else if (error instanceof Error) {
|
||||
Log.error(error.message);
|
||||
} else {
|
||||
Log.error(`Unexpected error: ${String(error)}`);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIfFileExistsAsync(filePath: string): Promise<void> {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
throw new Error(`File does not exist: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfValidYAMLFileExtension(filePath: string): void {
|
||||
const fileExtension = path.extname(filePath).toLowerCase();
|
||||
|
||||
if (fileExtension !== '.yml' && fileExtension !== '.yaml') {
|
||||
throw new Error('File must have a .yml or .yaml extension');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateYAMLAsync(filePath: string): Promise<void> {
|
||||
await checkIfFileExistsAsync(filePath);
|
||||
checkIfValidYAMLFileExtension(filePath);
|
||||
|
||||
try {
|
||||
const fileContents = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// First check if the file is empty or only contains whitespace
|
||||
if (!fileContents.trim()) {
|
||||
throw new Error('YAML file is empty or contains only comments.');
|
||||
}
|
||||
|
||||
const parsedYaml = YAML.parse(fileContents);
|
||||
|
||||
// Check if the parsed result is empty or null
|
||||
if (
|
||||
parsedYaml === null ||
|
||||
parsedYaml === undefined ||
|
||||
(typeof parsedYaml === 'object' && Object.keys(parsedYaml).length === 0)
|
||||
) {
|
||||
throw new Error('YAML file is empty or contains only comments.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === 'YAML file is empty or contains only comments.'
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
if (error instanceof YAML.YAMLParseError || error instanceof Error) {
|
||||
throw new Error(`YAML parsing error: ${error.message}`);
|
||||
} else {
|
||||
throw new Error(`YAML parsing error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export class CredentialsContext {
|
||||
public readonly analytics: Analytics;
|
||||
public readonly vcsClient: Client;
|
||||
public readonly easJsonCliConfig?: EasJson['cli'];
|
||||
public readonly usesBroadcastPushNotifications: boolean;
|
||||
|
||||
private shouldAskAuthenticateAppStore: boolean = true;
|
||||
|
||||
@@ -61,6 +62,8 @@ export class CredentialsContext {
|
||||
this.nonInteractive = options.nonInteractive ?? false;
|
||||
this.projectInfo = options.projectInfo;
|
||||
this.freezeCredentials = options.freezeCredentials ?? false;
|
||||
this.usesBroadcastPushNotifications =
|
||||
options.projectInfo?.exp.ios?.usesBroadcastPushNotifications ?? false;
|
||||
}
|
||||
|
||||
get hasProjectContext(): boolean {
|
||||
|
||||
@@ -35,7 +35,7 @@ export class SetUpTargetBuildCredentials {
|
||||
bundleIdentifier: app.bundleIdentifier,
|
||||
projectName: app.projectName,
|
||||
},
|
||||
{ entitlements }
|
||||
{ entitlements, usesBroadcastPushNotifications: ctx.usesBroadcastPushNotifications }
|
||||
);
|
||||
}
|
||||
try {
|
||||
|
||||
+283
-73
@@ -45,7 +45,9 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
await syncCapabilitiesForEntitlementsAsync(bundleId, entitlements);
|
||||
await syncCapabilitiesForEntitlementsAsync(bundleId, entitlements, {
|
||||
usesBroadcastPushNotifications: false,
|
||||
});
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{
|
||||
@@ -62,63 +64,70 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
// 'com.apple.developer.healthkit.access': [],
|
||||
'com.apple.developer.in-app-payments': ['merchant.com.example.development'],
|
||||
'com.apple.developer.icloud-container-identifiers': [],
|
||||
'com.apple.developer.ClassKit-environment': 'development',
|
||||
'com.apple.developer.default-data-protection':
|
||||
'NSFileProtectionCompleteUntilFirstUserAuthentication',
|
||||
'com.apple.developer.networking.networkextension': ['dns-proxy'],
|
||||
'com.apple.developer.networking.vpn.api': ['allow-vpn'],
|
||||
'com.apple.developer.networking.HotspotConfiguration': true,
|
||||
'com.apple.developer.kernel.extended-virtual-addressing': true,
|
||||
'com.apple.developer.homekit': true,
|
||||
'com.apple.developer.networking.multipath': true,
|
||||
'com.apple.external-accessory.wireless-configuration': true,
|
||||
'inter-app-audio': true,
|
||||
'com.apple.developer.pass-type-identifiers': ['$(TeamIdentifierPrefix)*'],
|
||||
'com.apple.developer.user-fonts': ['app-usage'],
|
||||
'com.apple.developer.devicecheck.appattest-environment': 'development',
|
||||
'com.apple.developer.nfc.readersession.formats': ['NDEF', 'TAG'],
|
||||
'com.apple.developer.applesignin': ['Default'],
|
||||
'com.apple.developer.siri': true,
|
||||
'com.apple.developer.networking.wifi-info': true,
|
||||
'com.apple.developer.usernotifications.communication': true,
|
||||
'com.apple.developer.usernotifications.time-sensitive': true,
|
||||
'com.apple.developer.group-session': true,
|
||||
'com.apple.developer.family-controls': true,
|
||||
'com.apple.developer.authentication-services.autofill-credential-provider': true,
|
||||
// 'com.apple.developer.game-center': true,
|
||||
'com.apple.security.application-groups': [
|
||||
'group.CY-A5149AC2-49FC-11E7-B3F3-0335A16FFB8D.com.cydia.Extender',
|
||||
],
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
// 'com.apple.developer.healthkit.access': [],
|
||||
'com.apple.developer.in-app-payments': ['merchant.com.example.development'],
|
||||
'com.apple.developer.icloud-container-identifiers': [],
|
||||
'com.apple.developer.ClassKit-environment': 'development',
|
||||
'com.apple.developer.default-data-protection':
|
||||
'NSFileProtectionCompleteUntilFirstUserAuthentication',
|
||||
'com.apple.developer.networking.networkextension': ['dns-proxy'],
|
||||
'com.apple.developer.networking.vpn.api': ['allow-vpn'],
|
||||
'com.apple.developer.networking.HotspotConfiguration': true,
|
||||
'com.apple.developer.kernel.extended-virtual-addressing': true,
|
||||
'com.apple.developer.homekit': true,
|
||||
'com.apple.developer.networking.multipath': true,
|
||||
'com.apple.external-accessory.wireless-configuration': true,
|
||||
'inter-app-audio': true,
|
||||
'com.apple.developer.pass-type-identifiers': ['$(TeamIdentifierPrefix)*'],
|
||||
'com.apple.developer.user-fonts': ['app-usage'],
|
||||
'com.apple.developer.devicecheck.appattest-environment': 'development',
|
||||
'com.apple.developer.nfc.readersession.formats': ['NDEF', 'TAG'],
|
||||
'com.apple.developer.applesignin': ['Default'],
|
||||
'com.apple.developer.siri': true,
|
||||
'com.apple.developer.networking.wifi-info': true,
|
||||
'com.apple.developer.usernotifications.communication': true,
|
||||
'com.apple.developer.usernotifications.time-sensitive': true,
|
||||
'com.apple.developer.group-session': true,
|
||||
'com.apple.developer.family-controls': true,
|
||||
'com.apple.developer.authentication-services.autofill-credential-provider': true,
|
||||
// 'com.apple.developer.game-center': true,
|
||||
'com.apple.security.application-groups': [
|
||||
'group.CY-A5149AC2-49FC-11E7-B3F3-0335A16FFB8D.com.cydia.Extender',
|
||||
],
|
||||
|
||||
'com.apple.developer.fileprovider.testing-mode': true,
|
||||
'com.apple.developer.healthkit.recalibrate-estimates': true,
|
||||
'com.apple.developer.maps': true,
|
||||
'com.apple.developer.user-management': true,
|
||||
'com.apple.developer.networking.custom-protocol': true,
|
||||
'com.apple.developer.system-extension.install': true,
|
||||
'com.apple.developer.push-to-talk': true,
|
||||
'com.apple.developer.driverkit.transport.usb': true,
|
||||
'com.apple.developer.kernel.increased-memory-limit': true,
|
||||
'com.apple.developer.driverkit.communicates-with-drivers': true,
|
||||
'com.apple.developer.media-device-discovery-extension': true,
|
||||
'com.apple.developer.driverkit.allow-third-party-userclients': true,
|
||||
'com.apple.developer.weatherkit': true,
|
||||
'com.apple.developer.on-demand-install-capable': true,
|
||||
'com.apple.developer.driverkit.family.scsicontroller': true,
|
||||
'com.apple.developer.driverkit.family.serial': true,
|
||||
'com.apple.developer.driverkit.family.networking': true,
|
||||
'com.apple.developer.driverkit.family.hid.eventservice': true,
|
||||
'com.apple.developer.driverkit.family.hid.device': true,
|
||||
'com.apple.developer.driverkit': true,
|
||||
'com.apple.developer.driverkit.transport.hid': true,
|
||||
'com.apple.developer.driverkit.family.audio': true,
|
||||
'com.apple.developer.shared-with-you': true,
|
||||
});
|
||||
'com.apple.developer.fileprovider.testing-mode': true,
|
||||
'com.apple.developer.healthkit.recalibrate-estimates': true,
|
||||
'com.apple.developer.maps': true,
|
||||
'com.apple.developer.user-management': true,
|
||||
'com.apple.developer.networking.custom-protocol': true,
|
||||
'com.apple.developer.system-extension.install': true,
|
||||
'com.apple.developer.push-to-talk': true,
|
||||
'com.apple.developer.driverkit.transport.usb': true,
|
||||
'com.apple.developer.kernel.increased-memory-limit': true,
|
||||
'com.apple.developer.driverkit.communicates-with-drivers': true,
|
||||
'com.apple.developer.media-device-discovery-extension': true,
|
||||
'com.apple.developer.driverkit.allow-third-party-userclients': true,
|
||||
'com.apple.developer.weatherkit': true,
|
||||
'com.apple.developer.on-demand-install-capable': true,
|
||||
'com.apple.developer.driverkit.family.scsicontroller': true,
|
||||
'com.apple.developer.driverkit.family.serial': true,
|
||||
'com.apple.developer.driverkit.family.networking': true,
|
||||
'com.apple.developer.driverkit.family.hid.eventservice': true,
|
||||
'com.apple.developer.driverkit.family.hid.device': true,
|
||||
'com.apple.developer.driverkit': true,
|
||||
'com.apple.developer.driverkit.transport.hid': true,
|
||||
'com.apple.developer.driverkit.family.audio': true,
|
||||
'com.apple.developer.shared-with-you': true,
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'HEALTHKIT', option: 'ON' },
|
||||
@@ -251,6 +260,10 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
capabilityType: 'SHARED_WITH_YOU',
|
||||
option: 'ON',
|
||||
},
|
||||
{
|
||||
capabilityType: 'PUSH_NOTIFICATIONS',
|
||||
option: 'ON',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(results.enabled).toMatchInlineSnapshot(`
|
||||
@@ -304,6 +317,7 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
"DriverKit Transport HID (development)",
|
||||
"DriverKit Family Audio (development)",
|
||||
"Shared with You",
|
||||
"Push Notifications",
|
||||
]
|
||||
`);
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
@@ -318,9 +332,15 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
});
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).not.toBeCalled();
|
||||
|
||||
@@ -344,9 +364,15 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
} as any;
|
||||
|
||||
await expect(
|
||||
syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
})
|
||||
syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`https://developer-mdn.apple.com/account/resources/identifiers/bundleId/edit/XXX333`
|
||||
);
|
||||
@@ -361,9 +387,15 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
});
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'HEALTHKIT', option: 'ON' },
|
||||
@@ -382,9 +414,15 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
});
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'HEALTHKIT', option: 'ON' },
|
||||
@@ -406,7 +444,13 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {});
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).not.toBeCalled();
|
||||
|
||||
@@ -423,9 +467,15 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.healthkit': true,
|
||||
});
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'com.apple.developer.healthkit': true,
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'HEALTHKIT', option: 'ON' },
|
||||
@@ -434,4 +484,164 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
expect(results.enabled).toStrictEqual(['HealthKit']);
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('enables push notifications capability with broadcast option when the capability is disabled and usesBroadcastPushNotifications is true', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => []),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'PUSH_NOTIFICATIONS', option: 'PUSH_NOTIFICATION_FEATURE_BROADCAST' },
|
||||
]);
|
||||
|
||||
expect(results.enabled).toStrictEqual(['Push Notifications']);
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('updates push notifications capability with broadcast option when the capability is enabled without settings and usesBroadcastPushNotifications is true', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => [
|
||||
new BundleIdCapability({}, 'XXX_PUSH_NOTIFICATIONS', { settings: null }),
|
||||
]),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'PUSH_NOTIFICATIONS', option: 'PUSH_NOTIFICATION_FEATURE_BROADCAST' },
|
||||
]);
|
||||
|
||||
expect(results.enabled).toStrictEqual(['Push Notifications']);
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('disables push notifications capability', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => [
|
||||
new BundleIdCapability({}, 'XXX_PUSH_NOTIFICATIONS', { settings: null }),
|
||||
]),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{},
|
||||
{
|
||||
usesBroadcastPushNotifications: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'PUSH_NOTIFICATIONS', option: 'OFF' },
|
||||
]);
|
||||
|
||||
expect(results.disabled).toStrictEqual(['Push Notifications']);
|
||||
expect(results.enabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('does nothing when push notifications capability is enabled with broadcast options and usesBroadcastPushNotifications is true', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => [
|
||||
new BundleIdCapability({}, 'XXX_PUSH_NOTIFICATIONS', {
|
||||
settings: [],
|
||||
}),
|
||||
]),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).not.toBeCalled();
|
||||
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
expect(results.enabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('does nothing when push notifications capability is enabled without broadcast options and usesBroadcastPushNotifications is false', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => [
|
||||
new BundleIdCapability({}, 'XXX_PUSH_NOTIFICATIONS', {
|
||||
settings: null,
|
||||
}),
|
||||
]),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).not.toBeCalled();
|
||||
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
expect(results.enabled).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('updates push notifications capability without broadcast options when it is enabled and usesBroadcastPushNotifications is false', async () => {
|
||||
const bundleId = {
|
||||
getBundleIdCapabilitiesAsync: jest.fn(() => [
|
||||
new BundleIdCapability({}, 'XXX_PUSH_NOTIFICATIONS', {
|
||||
settings: [],
|
||||
}),
|
||||
]),
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
const results = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
{
|
||||
'aps-environment': 'production',
|
||||
},
|
||||
{
|
||||
usesBroadcastPushNotifications: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{ capabilityType: 'PUSH_NOTIFICATIONS', option: 'ON' },
|
||||
]);
|
||||
|
||||
expect(results.disabled).toStrictEqual([]);
|
||||
expect(results.enabled).toStrictEqual(['Push Notifications']);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
CapabilityType,
|
||||
CapabilityTypeDataProtectionOption,
|
||||
CapabilityTypeOption,
|
||||
CapabilityTypePushNotificationsOption,
|
||||
CloudContainer,
|
||||
MerchantId,
|
||||
} from '@expo/apple-utils';
|
||||
@@ -19,7 +20,10 @@ export const EXPO_NO_CAPABILITY_SYNC = getenv.boolish('EXPO_NO_CAPABILITY_SYNC',
|
||||
|
||||
type GetOptionsMethod<T extends CapabilityType = any> = (
|
||||
entitlement: JSONValue,
|
||||
entitlementsJson: JSONObject
|
||||
entitlementsJson: JSONObject,
|
||||
additionalOptions: {
|
||||
usesBroadcastPushNotifications?: boolean;
|
||||
}
|
||||
) => CapabilityOptionMap[T];
|
||||
|
||||
const validateBooleanOptions = (options: any): boolean => {
|
||||
@@ -78,7 +82,10 @@ const getDefinedOptions: GetOptionsMethod = entitlement => {
|
||||
*/
|
||||
export async function syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId: BundleId,
|
||||
entitlements: JSONObject = {}
|
||||
entitlements: JSONObject = {},
|
||||
additionalOptions: {
|
||||
usesBroadcastPushNotifications: boolean;
|
||||
}
|
||||
): Promise<{ enabled: string[]; disabled: string[] }> {
|
||||
if (EXPO_NO_CAPABILITY_SYNC) {
|
||||
return { enabled: [], disabled: [] };
|
||||
@@ -91,7 +98,8 @@ export async function syncCapabilitiesForEntitlementsAsync(
|
||||
|
||||
const { enabledCapabilityNames, request, remainingCapabilities } = getCapabilitiesToEnable(
|
||||
currentCapabilities,
|
||||
entitlements
|
||||
entitlements,
|
||||
additionalOptions
|
||||
);
|
||||
|
||||
const { disabledCapabilityNames, request: modifiedRequest } = getCapabilitiesToDisable(
|
||||
@@ -125,9 +133,25 @@ interface CapabilitiesRequest {
|
||||
option: any;
|
||||
}
|
||||
|
||||
function shouldSkipPushNotificationsCapabilityUpdate(
|
||||
existing: BundleIdCapability,
|
||||
additionalOptions: {
|
||||
usesBroadcastPushNotifications: boolean;
|
||||
}
|
||||
): boolean {
|
||||
// For push notifications, we should always update the capabaility if
|
||||
// - settings are not defined in the existing capability, but usesBroadcastPushNotifications is enabled (we want to add settings for this capability)
|
||||
// - settings are defined in the existing capability, but usesBroadcastPushNotifications is disabled (we want to remove settings for this capability)
|
||||
const noSettingsAttributes = existing.attributes.settings == null;
|
||||
return !noSettingsAttributes === additionalOptions.usesBroadcastPushNotifications;
|
||||
}
|
||||
|
||||
function getCapabilitiesToEnable(
|
||||
currentCapabilities: BundleIdCapability[],
|
||||
entitlements: JSONObject
|
||||
entitlements: JSONObject,
|
||||
additionalOptions: {
|
||||
usesBroadcastPushNotifications: boolean;
|
||||
}
|
||||
): {
|
||||
enabledCapabilityNames: string[];
|
||||
request: CapabilitiesRequest[];
|
||||
@@ -156,7 +180,17 @@ function getCapabilitiesToEnable(
|
||||
// Only skip if the existing capability is a simple boolean value,
|
||||
// if it has more complex settings then we should always update it.
|
||||
// If the `existing.attributes.settings` object is defined, then we can determine that it has extra configuration.
|
||||
if (existing && existing?.attributes.settings == null) {
|
||||
// For push notifications, we should always update the capabaility if
|
||||
// - settings are not defined in the existing capability, but usesBroadcastPushNotifications is enabled (we want to add settings for this capability)
|
||||
// - settings are defined in the existing capability, but usesBroadcastPushNotifications is disabled (we want to remove settings for this capability)
|
||||
const isPushNotificationsCapability =
|
||||
staticCapabilityInfo.capability === CapabilityType.PUSH_NOTIFICATIONS;
|
||||
if (
|
||||
existing &&
|
||||
((!isPushNotificationsCapability && existing.attributes.settings == null) ||
|
||||
(isPushNotificationsCapability &&
|
||||
shouldSkipPushNotificationsCapabilityUpdate(existing, additionalOptions)))
|
||||
) {
|
||||
// Remove the item from the list of capabilities so we don't disable it.
|
||||
remainingCapabilities.splice(existingIndex, 1);
|
||||
if (Log.isDebug) {
|
||||
@@ -173,7 +207,7 @@ function getCapabilitiesToEnable(
|
||||
|
||||
enabledCapabilityNames.push(staticCapabilityInfo.name);
|
||||
|
||||
const option = staticCapabilityInfo.getOptions(value, entitlements);
|
||||
const option = staticCapabilityInfo.getOptions(value, entitlements, additionalOptions);
|
||||
|
||||
request.push({
|
||||
capabilityType: staticCapabilityInfo.capability,
|
||||
@@ -488,7 +522,13 @@ export const CapabilityMapping: CapabilityClassifier[] = [
|
||||
entitlement: 'aps-environment',
|
||||
capability: CapabilityType.PUSH_NOTIFICATIONS,
|
||||
validateOptions: validateDevProdString,
|
||||
getOptions: getDefinedOptions,
|
||||
getOptions(entitlement, _entitlementsJson, { usesBroadcastPushNotifications }) {
|
||||
const option = entitlement ? CapabilityTypeOption.ON : CapabilityTypeOption.OFF;
|
||||
if (option === CapabilityTypeOption.ON && usesBroadcastPushNotifications) {
|
||||
return CapabilityTypePushNotificationsOption.PUSH_NOTIFICATION_FEATURE_BROADCAST;
|
||||
}
|
||||
return option;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Wallet',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ora } from '../../../ora';
|
||||
|
||||
export interface IosCapabilitiesOptions {
|
||||
entitlements: JSONObject;
|
||||
usesBroadcastPushNotifications: boolean;
|
||||
}
|
||||
|
||||
export interface AppLookupParams {
|
||||
@@ -93,7 +94,7 @@ export async function ensureBundleIdExistsWithNameAsync(
|
||||
|
||||
export async function syncCapabilitiesAsync(
|
||||
bundleId: BundleId,
|
||||
{ entitlements }: IosCapabilitiesOptions
|
||||
{ entitlements, ...rest }: IosCapabilitiesOptions
|
||||
): Promise<void> {
|
||||
const spinner = ora(`Syncing capabilities`).start();
|
||||
|
||||
@@ -105,7 +106,8 @@ export async function syncCapabilitiesAsync(
|
||||
try {
|
||||
const { enabled, disabled } = await syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId,
|
||||
entitlements
|
||||
entitlements,
|
||||
rest
|
||||
);
|
||||
const results =
|
||||
[buildMessage('Enabled', enabled), buildMessage('Disabled', disabled)]
|
||||
@@ -119,7 +121,7 @@ export async function syncCapabilitiesAsync(
|
||||
}
|
||||
|
||||
// Always run this after syncing the capabilities...
|
||||
await syncCapabilityIdentifiersAsync(bundleId, { entitlements });
|
||||
await syncCapabilityIdentifiersAsync(bundleId, { entitlements, ...rest });
|
||||
}
|
||||
|
||||
const buildMessage = (title: string, items: string[]): string =>
|
||||
|
||||
+128
-50
@@ -711,6 +711,7 @@ export type AccountSsoConfigurationPublicDataQueryPublicDataByAccountNameArgs =
|
||||
};
|
||||
|
||||
export enum AccountUploadSessionType {
|
||||
ProfileImageUpload = 'PROFILE_IMAGE_UPLOAD',
|
||||
WorkflowsProjectSources = 'WORKFLOWS_PROJECT_SOURCES'
|
||||
}
|
||||
|
||||
@@ -767,7 +768,8 @@ export enum ActivityTimelineProjectActivityType {
|
||||
Build = 'BUILD',
|
||||
Submission = 'SUBMISSION',
|
||||
Update = 'UPDATE',
|
||||
Worker = 'WORKER'
|
||||
Worker = 'WORKER',
|
||||
WorkflowRun = 'WORKFLOW_RUN'
|
||||
}
|
||||
|
||||
/** A regular user, SSO user, or robot that can authenticate with Expo services and be a member of accounts. */
|
||||
@@ -1309,8 +1311,9 @@ export type App = Project & {
|
||||
* @deprecated Classic updates have been deprecated.
|
||||
*/
|
||||
playStoreUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Use 'privacySetting' instead. */
|
||||
/** @deprecated No longer supported */
|
||||
privacy: Scalars['String']['output'];
|
||||
/** @deprecated No longer supported */
|
||||
privacySetting: AppPrivacy;
|
||||
/**
|
||||
* Whether there have been any classic update publishes
|
||||
@@ -1745,7 +1748,6 @@ export type AppInfoInput = {
|
||||
export type AppInput = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
appInfo?: InputMaybe<AppInfoInput>;
|
||||
privacy: AppPrivacy;
|
||||
projectName: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
@@ -2047,6 +2049,10 @@ export type AppUpdatesConnection = {
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export enum AppUploadSessionType {
|
||||
ProfileImageUpload = 'PROFILE_IMAGE_UPLOAD'
|
||||
}
|
||||
|
||||
/** Represents Play Store/App Store version of an application */
|
||||
export type AppVersion = {
|
||||
__typename?: 'AppVersion';
|
||||
@@ -2097,7 +2103,6 @@ export type AppWithGithubRepositoryInput = {
|
||||
accountId: Scalars['ID']['input'];
|
||||
appInfo?: InputMaybe<AppInfoInput>;
|
||||
installationIdentifier?: InputMaybe<Scalars['String']['input']>;
|
||||
privacy: AppPrivacy;
|
||||
projectName: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
@@ -2689,6 +2694,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
error?: Maybe<BuildError>;
|
||||
estimatedWaitTimeLeftSeconds?: Maybe<Scalars['Int']['output']>;
|
||||
expirationDate?: Maybe<Scalars['DateTime']['output']>;
|
||||
fingerprint?: Maybe<Fingerprint>;
|
||||
gitCommitHash?: Maybe<Scalars['String']['output']>;
|
||||
gitCommitMessage?: Maybe<Scalars['String']['output']>;
|
||||
gitRef?: Maybe<Scalars['String']['output']>;
|
||||
@@ -2722,6 +2728,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
reactNativeVersion?: Maybe<Scalars['String']['output']>;
|
||||
releaseChannel?: Maybe<Scalars['String']['output']>;
|
||||
requiredPackageManager?: Maybe<Scalars['String']['output']>;
|
||||
resolvedEnvironment?: Maybe<EnvironmentVariableEnvironment>;
|
||||
/**
|
||||
* The builder resource class requested by the developer
|
||||
* @deprecated Use resourceClassDisplayName instead
|
||||
@@ -2864,6 +2871,7 @@ export type BuildFilter = {
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
distribution?: InputMaybe<DistributionType>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
|
||||
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
platform?: InputMaybe<AppPlatform>;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
sdkVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2875,6 +2883,7 @@ export type BuildFilterInput = {
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
developmentClient?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
distributions?: InputMaybe<Array<DistributionType>>;
|
||||
hasFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
platforms?: InputMaybe<Array<AppPlatform>>;
|
||||
releaseChannel?: InputMaybe<Scalars['String']['input']>;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -3388,12 +3397,6 @@ export type CreateIosSubmissionInput = {
|
||||
submittedBuildId?: InputMaybe<Scalars['ID']['input']>;
|
||||
};
|
||||
|
||||
export type CreateServerlessFunctionUploadUrlResult = {
|
||||
__typename?: 'CreateServerlessFunctionUploadUrlResult';
|
||||
formDataFields: Scalars['JSONObject']['output'];
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type CreateSharedEnvironmentVariableInput = {
|
||||
environments?: InputMaybe<Array<EnvironmentVariableEnvironment>>;
|
||||
fileName?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -3629,11 +3632,6 @@ export type DeleteWorkerDeploymentResult = {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeployServerlessFunctionResult = {
|
||||
__typename?: 'DeployServerlessFunctionResult';
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
/** Represents a Deployment - a set of Builds with the same Runtime Version and Channel */
|
||||
export type Deployment = {
|
||||
__typename?: 'Deployment';
|
||||
@@ -4179,6 +4177,35 @@ export enum Feature {
|
||||
Teams = 'TEAMS'
|
||||
}
|
||||
|
||||
export type Fingerprint = {
|
||||
__typename?: 'Fingerprint';
|
||||
app: App;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
debugInfoUrl?: Maybe<Scalars['String']['output']>;
|
||||
hash: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
source?: Maybe<FingerprintSource>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type FingerprintInfo = {
|
||||
fingerprintHash: Scalars['String']['input'];
|
||||
fingerprintSource: FingerprintSourceInput;
|
||||
};
|
||||
|
||||
export type FingerprintInfoGroup = {
|
||||
android?: InputMaybe<FingerprintInfo>;
|
||||
ios?: InputMaybe<FingerprintInfo>;
|
||||
web?: InputMaybe<FingerprintInfo>;
|
||||
};
|
||||
|
||||
export type FingerprintSource = {
|
||||
__typename?: 'FingerprintSource';
|
||||
bucketKey: Scalars['String']['output'];
|
||||
isDebugFingerprint?: Maybe<Scalars['Boolean']['output']>;
|
||||
type: FingerprintSourceType;
|
||||
};
|
||||
|
||||
export type FingerprintSourceInput = {
|
||||
bucketKey?: InputMaybe<Scalars['String']['input']>;
|
||||
isDebugFingerprint?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
@@ -4975,6 +5002,7 @@ export type JobRun = {
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
displayName?: Maybe<Scalars['String']['output']>;
|
||||
endedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
errors: Array<JobRunError>;
|
||||
expiresAt: Scalars['DateTime']['output'];
|
||||
gitCommitHash?: Maybe<Scalars['String']['output']>;
|
||||
gitCommitMessage?: Maybe<Scalars['String']['output']>;
|
||||
@@ -4990,6 +5018,14 @@ export type JobRun = {
|
||||
updateGroups: Array<Array<Update>>;
|
||||
};
|
||||
|
||||
export type JobRunError = {
|
||||
__typename?: 'JobRunError';
|
||||
buildPhase?: Maybe<Scalars['String']['output']>;
|
||||
docsUrl?: Maybe<Scalars['String']['output']>;
|
||||
errorCode: Scalars['String']['output'];
|
||||
message: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type JobRunMutation = {
|
||||
__typename?: 'JobRunMutation';
|
||||
/** Cancel an EAS Job Run */
|
||||
@@ -5477,6 +5513,7 @@ export type PublishUpdateGroupInput = {
|
||||
awaitingCodeSigningInfo?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
branchId: Scalars['String']['input'];
|
||||
excludedAssets?: InputMaybe<Array<PartialManifestAsset>>;
|
||||
fingerprintInfoGroup?: InputMaybe<FingerprintInfoGroup>;
|
||||
gitCommitHash?: InputMaybe<Scalars['String']['input']>;
|
||||
isGitWorkingTreeDirty?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
message?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -5722,7 +5759,6 @@ export type RootMutation = {
|
||||
notificationSubscription: NotificationSubscriptionMutation;
|
||||
/** Mutations that create, update, and delete Robots */
|
||||
robot: RobotMutation;
|
||||
serverlessFunction: ServerlessFunctionMutation;
|
||||
/** Mutations that modify an EAS Submit submission */
|
||||
submission: SubmissionMutation;
|
||||
update: UpdateMutation;
|
||||
@@ -5739,6 +5775,7 @@ export type RootMutation = {
|
||||
/** Mutations that modify a websiteNotification */
|
||||
websiteNotifications: WebsiteNotificationMutation;
|
||||
workflowJob: WorkflowJobMutation;
|
||||
workflowRevision: WorkflowRevisionMutation;
|
||||
workflowRun: WorkflowRunMutation;
|
||||
};
|
||||
|
||||
@@ -6124,28 +6161,6 @@ export type SecondFactorRegenerateBackupCodesResult = {
|
||||
plaintextBackupCodes: Array<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type ServerlessFunctionIdentifierInput = {
|
||||
gitCommitSHA1: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type ServerlessFunctionMutation = {
|
||||
__typename?: 'ServerlessFunctionMutation';
|
||||
createDeployment: DeployServerlessFunctionResult;
|
||||
createUploadPresignedUrl: CreateServerlessFunctionUploadUrlResult;
|
||||
};
|
||||
|
||||
|
||||
export type ServerlessFunctionMutationCreateDeploymentArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
serverlessFunctionIdentifierInput: ServerlessFunctionIdentifierInput;
|
||||
};
|
||||
|
||||
|
||||
export type ServerlessFunctionMutationCreateUploadPresignedUrlArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
serverlessFunctionIdentifierInput: ServerlessFunctionIdentifierInput;
|
||||
};
|
||||
|
||||
export type Snack = Project & {
|
||||
__typename?: 'Snack';
|
||||
/** Description of the Snack */
|
||||
@@ -6535,6 +6550,7 @@ export type Update = ActivityTimelineProjectActivity & {
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
deployments: DeploymentResult;
|
||||
expoGoSDKVersion?: Maybe<Scalars['String']['output']>;
|
||||
fingerprint?: Maybe<Fingerprint>;
|
||||
gitCommitHash?: Maybe<Scalars['String']['output']>;
|
||||
group: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
@@ -6848,6 +6864,8 @@ export type UploadSession = {
|
||||
__typename?: 'UploadSession';
|
||||
/** Create an Upload Session for a specific account */
|
||||
createAccountScopedUploadSession: Scalars['JSONObject']['output'];
|
||||
/** Create an Upload Session for a specific app */
|
||||
createAppScopedUploadSession: Scalars['JSONObject']['output'];
|
||||
/** Create an Upload Session */
|
||||
createUploadSession: Scalars['JSONObject']['output'];
|
||||
};
|
||||
@@ -6859,6 +6877,12 @@ export type UploadSessionCreateAccountScopedUploadSessionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type UploadSessionCreateAppScopedUploadSessionArgs = {
|
||||
appID: Scalars['ID']['input'];
|
||||
type: AppUploadSessionType;
|
||||
};
|
||||
|
||||
|
||||
export type UploadSessionCreateUploadSessionArgs = {
|
||||
type: UploadSessionType;
|
||||
};
|
||||
@@ -7627,6 +7651,7 @@ export type WorkerDeployment = ActivityTimelineProjectActivity & {
|
||||
logs?: Maybe<WorkerDeploymentLogs>;
|
||||
requests?: Maybe<WorkerDeploymentRequests>;
|
||||
subdomain: Scalars['String']['output'];
|
||||
tier?: Maybe<WorkerDeploymentTier>;
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
@@ -7979,6 +8004,11 @@ export type WorkerDeploymentRequestsTimeseriesEdge = {
|
||||
timestamp: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export enum WorkerDeploymentTier {
|
||||
Free = 'FREE',
|
||||
Paid = 'PAID'
|
||||
}
|
||||
|
||||
export type WorkerDeploymentsConnection = {
|
||||
__typename?: 'WorkerDeploymentsConnection';
|
||||
edges: Array<WorkerDeploymentEdge>;
|
||||
@@ -8073,6 +8103,7 @@ export enum WorkflowJobStatus {
|
||||
Failure = 'FAILURE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
New = 'NEW',
|
||||
PendingCancel = 'PENDING_CANCEL',
|
||||
Skipped = 'SKIPPED',
|
||||
Success = 'SUCCESS'
|
||||
}
|
||||
@@ -8081,6 +8112,7 @@ export enum WorkflowJobType {
|
||||
AppleDeviceRegistrationRequest = 'APPLE_DEVICE_REGISTRATION_REQUEST',
|
||||
Build = 'BUILD',
|
||||
Custom = 'CUSTOM',
|
||||
GetBuild = 'GET_BUILD',
|
||||
MaestroTest = 'MAESTRO_TEST',
|
||||
RequireApproval = 'REQUIRE_APPROVAL',
|
||||
Submission = 'SUBMISSION',
|
||||
@@ -8130,6 +8162,17 @@ export type WorkflowRevisionInput = {
|
||||
yamlConfig: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkflowRevisionMutation = {
|
||||
__typename?: 'WorkflowRevisionMutation';
|
||||
validateWorkflowYamlConfig: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowRevisionMutationValidateWorkflowYamlConfigArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
yamlConfig: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type WorkflowRevisionQuery = {
|
||||
__typename?: 'WorkflowRevisionQuery';
|
||||
byId: WorkflowRevision;
|
||||
@@ -8146,9 +8189,12 @@ export type WorkflowRevisionsConnection = {
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type WorkflowRun = {
|
||||
export type WorkflowRun = ActivityTimelineProjectActivity & {
|
||||
__typename?: 'WorkflowRun';
|
||||
activityTimestamp: Scalars['DateTime']['output'];
|
||||
actor?: Maybe<Actor>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
errors: Array<WorkflowRunError>;
|
||||
gitCommitHash?: Maybe<Scalars['String']['output']>;
|
||||
gitCommitMessage?: Maybe<Scalars['String']['output']>;
|
||||
githubRepository?: Maybe<GitHubRepository>;
|
||||
@@ -8170,16 +8216,28 @@ export type WorkflowRunEdge = {
|
||||
node: WorkflowRun;
|
||||
};
|
||||
|
||||
export type WorkflowRunError = {
|
||||
__typename?: 'WorkflowRunError';
|
||||
message: Scalars['String']['output'];
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type WorkflowRunInput = {
|
||||
projectSource: WorkflowProjectSourceInput;
|
||||
};
|
||||
|
||||
export type WorkflowRunMutation = {
|
||||
__typename?: 'WorkflowRunMutation';
|
||||
cancelWorkflowRun: WorkflowRun;
|
||||
createWorkflowRun: WorkflowRun;
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowRunMutationCancelWorkflowRunArgs = {
|
||||
workflowRunId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowRunMutationCreateWorkflowRunArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
workflowRevisionInput: WorkflowRevisionInput;
|
||||
@@ -8202,6 +8260,7 @@ export enum WorkflowRunStatus {
|
||||
Failure = 'FAILURE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
New = 'NEW',
|
||||
PendingCancel = 'PENDING_CANCEL',
|
||||
Success = 'SUCCESS'
|
||||
}
|
||||
|
||||
@@ -8884,7 +8943,7 @@ export type UpdatePublishMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePublishMutation = { __typename?: 'RootMutation', updateBranch: { __typename?: 'UpdateBranchMutation', publishUpdateGroups: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }> } };
|
||||
export type UpdatePublishMutation = { __typename?: 'RootMutation', updateBranch: { __typename?: 'UpdateBranchMutation', publishUpdateGroups: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> } };
|
||||
|
||||
export type SetCodeSigningInfoMutationVariables = Exact<{
|
||||
updateId: Scalars['ID']['input'];
|
||||
@@ -8900,7 +8959,7 @@ export type SetRolloutPercentageMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SetRolloutPercentageMutation = { __typename?: 'RootMutation', update: { __typename?: 'UpdateMutation', setRolloutPercentage: { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null } } };
|
||||
export type SetRolloutPercentageMutation = { __typename?: 'RootMutation', update: { __typename?: 'UpdateMutation', setRolloutPercentage: { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null } } };
|
||||
|
||||
export type CreateAndroidSubmissionMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
@@ -8967,6 +9026,14 @@ export type DeleteWebhookMutationVariables = Exact<{
|
||||
|
||||
export type DeleteWebhookMutation = { __typename?: 'RootMutation', webhook: { __typename?: 'WebhookMutation', deleteWebhook: { __typename?: 'DeleteWebhookResult', id: string } } };
|
||||
|
||||
export type ValidateWorkflowYamlConfigMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
yamlConfig: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ValidateWorkflowYamlConfigMutation = { __typename?: 'RootMutation', workflowRevision: { __typename?: 'WorkflowRevisionMutation', validateWorkflowYamlConfig: boolean } };
|
||||
|
||||
export type CreateWorkflowRunMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
workflowRevisionInput: WorkflowRevisionInput;
|
||||
@@ -9031,7 +9098,7 @@ export type BranchesByAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }> }> } } };
|
||||
export type BranchesByAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> }> } } };
|
||||
|
||||
export type BranchesBasicPaginatedOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9052,7 +9119,7 @@ export type ViewBranchesOnUpdateChannelQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }>> }> } | null } } };
|
||||
export type ViewBranchesOnUpdateChannelQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
|
||||
export type BuildsByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
@@ -9068,6 +9135,13 @@ export type BuildsWithSubmissionsByIdQueryVariables = Exact<{
|
||||
|
||||
export type BuildsWithSubmissionsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
|
||||
export type BuildsWithFingerprintByIdQueryVariables = Exact<{
|
||||
buildId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type BuildsWithFingerprintByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
|
||||
|
||||
export type ViewBuildsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
offset: Scalars['Int']['input'];
|
||||
@@ -9085,7 +9159,7 @@ export type ViewUpdateChannelOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }>> }> } | null } } };
|
||||
export type ViewUpdateChannelOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannelByName?: { __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> } | null } } };
|
||||
|
||||
export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9094,7 +9168,7 @@ export type ViewUpdateChannelsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }>> }> }> } } };
|
||||
export type ViewUpdateChannelsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateChannels: Array<{ __typename?: 'UpdateChannel', id: string, isPaused: boolean, name: string, updatedAt: any, createdAt: any, branchMapping: string, updateBranches: Array<{ __typename?: 'UpdateBranch', id: string, name: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> }> }> } } };
|
||||
|
||||
export type ViewUpdateChannelsPaginatedOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9218,7 +9292,7 @@ export type ViewUpdatesByGroupQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }> };
|
||||
export type ViewUpdatesByGroupQuery = { __typename?: 'RootQuery', updatesByGroup: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9229,7 +9303,7 @@ export type ViewUpdateGroupsOnBranchQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }>> } | null } } };
|
||||
export type ViewUpdateGroupsOnBranchQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateBranchByName?: { __typename?: 'UpdateBranch', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } | null } } };
|
||||
|
||||
export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -9239,7 +9313,7 @@ export type ViewUpdateGroupsOnAppQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }>> } } };
|
||||
export type ViewUpdateGroupsOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, updateGroups: Array<Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }>> } } };
|
||||
|
||||
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -9269,21 +9343,25 @@ export type BuildFragment = { __typename?: 'Build', id: string, status: BuildSta
|
||||
|
||||
export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, submissions: Array<{ __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null }>, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
|
||||
export type BuildWithFingerprintFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
|
||||
|
||||
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, type: EnvironmentSecretType, createdAt: any };
|
||||
|
||||
export type EnvironmentVariableFragment = { __typename?: 'EnvironmentVariable', id: string, name: string, value?: string | null, environments?: Array<EnvironmentVariableEnvironment> | null, createdAt: any, updatedAt: any, scope: EnvironmentVariableScope, visibility?: EnvironmentVariableVisibility | null, type: EnvironmentSecretType };
|
||||
|
||||
export type EnvironmentVariableWithSecretFragment = { __typename?: 'EnvironmentVariableWithSecret', id: string, name: string, value?: string | null, environments?: Array<EnvironmentVariableEnvironment> | null, createdAt: any, updatedAt: any, scope: EnvironmentVariableScope, visibility: EnvironmentVariableVisibility, type: EnvironmentSecretType };
|
||||
|
||||
export type FingerprintFragment = { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null };
|
||||
|
||||
export type RuntimeFragment = { __typename?: 'Runtime', id: string, version: string };
|
||||
|
||||
export type StatuspageServiceFragment = { __typename?: 'StatuspageService', id: string, name: StatuspageServiceName, status: StatuspageServiceStatus, incidents: Array<{ __typename?: 'StatuspageIncident', id: string, status: StatuspageIncidentStatus, name: string, impact: StatuspageIncidentImpact, shortlink: string }> };
|
||||
|
||||
export type SubmissionFragment = { __typename?: 'Submission', id: string, status: SubmissionStatus, platform: AppPlatform, logFiles: Array<string>, app: { __typename?: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, androidConfig?: { __typename?: 'AndroidSubmissionConfig', applicationIdentifier?: string | null, track: SubmissionAndroidTrack, releaseStatus?: SubmissionAndroidReleaseStatus | null, rollout?: number | null } | null, iosConfig?: { __typename?: 'IosSubmissionConfig', ascAppIdentifier: string, appleIdUsername?: string | null } | null, error?: { __typename?: 'SubmissionError', errorCode?: string | null, message?: string | null } | null };
|
||||
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null };
|
||||
export type UpdateFragment = { __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null };
|
||||
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null }> };
|
||||
export type UpdateBranchFragment = { __typename?: 'UpdateBranch', id: string, name: string, updates: Array<{ __typename?: 'Update', id: string, group: string, message?: string | null, createdAt: any, runtimeVersion: string, platform: string, manifestFragment: string, isRollBackToEmbedded: boolean, manifestPermalink: string, gitCommitHash?: string | null, rolloutPercentage?: number | null, actor?: { __typename: 'Robot', firstName?: string | null, id: string } | { __typename: 'SSOUser', username: string, id: string } | { __typename: 'User', username: string, id: string } | null, branch: { __typename?: 'UpdateBranch', id: string, name: string }, codeSigningInfo?: { __typename?: 'CodeSigningInfo', keyid: string, sig: string, alg: string } | null, rolloutControlUpdate?: { __typename?: 'Update', id: string } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, source?: { __typename?: 'FingerprintSource', type: FingerprintSourceType, bucketKey: string, isDebugFingerprint?: boolean | null } | null } | null }> };
|
||||
|
||||
export type UpdateBranchBasicInfoFragment = { __typename?: 'UpdateBranch', id: string, name: string };
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import { AppPrivacy, CreateAppMutation, CreateAppMutationVariables } from '../generated';
|
||||
import { CreateAppMutation, CreateAppMutationVariables } from '../generated';
|
||||
|
||||
export const AppMutation = {
|
||||
async createAppAsync(
|
||||
@@ -11,7 +11,6 @@ export const AppMutation = {
|
||||
appInput: {
|
||||
accountId: string;
|
||||
projectName: string;
|
||||
privacy: AppPrivacy;
|
||||
}
|
||||
): Promise<string> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
|
||||
@@ -24,6 +24,7 @@ type CreateVariableArgs = {
|
||||
visibility: EnvironmentVariableVisibility;
|
||||
environments: EnvironmentVariableEnvironment[];
|
||||
type: EnvironmentSecretType;
|
||||
isGlobal?: boolean;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
ValidateWorkflowYamlConfigMutation,
|
||||
ValidateWorkflowYamlConfigMutationVariables,
|
||||
} from '../generated';
|
||||
|
||||
export namespace WorkflowRevisionMutation {
|
||||
export const ValidationErrorExtensionZ = z.object({
|
||||
errorCode: z.literal('VALIDATION_ERROR'),
|
||||
metadata: z.object({
|
||||
formErrors: z.array(z.string()),
|
||||
fieldErrors: z.record(z.string(), z.array(z.string())),
|
||||
}),
|
||||
});
|
||||
|
||||
export async function validateWorkflowYamlConfigAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
yamlConfig,
|
||||
}: {
|
||||
appId: string;
|
||||
yamlConfig: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<ValidateWorkflowYamlConfigMutation, ValidateWorkflowYamlConfigMutationVariables>(
|
||||
gql`
|
||||
mutation ValidateWorkflowYamlConfig($appId: ID!, $yamlConfig: String!) {
|
||||
workflowRevision {
|
||||
validateWorkflowYamlConfig(appId: $appId, yamlConfig: $yamlConfig)
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
appId,
|
||||
yamlConfig,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,22 @@ import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/creat
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
BuildFragment,
|
||||
BuildWithFingerprintFragment,
|
||||
BuildWithSubmissionsFragment,
|
||||
BuildsByIdQuery,
|
||||
BuildsByIdQueryVariables,
|
||||
BuildsWithFingerprintByIdQuery,
|
||||
BuildsWithFingerprintByIdQueryVariables,
|
||||
BuildsWithSubmissionsByIdQuery,
|
||||
BuildsWithSubmissionsByIdQueryVariables,
|
||||
ViewBuildsOnAppQuery,
|
||||
ViewBuildsOnAppQueryVariables,
|
||||
} from '../generated';
|
||||
import { BuildFragmentNode, BuildFragmentWithSubmissionsNode } from '../types/Build';
|
||||
import {
|
||||
BuildFragmentNode,
|
||||
BuildFragmentWithFingerprintNode,
|
||||
BuildFragmentWithSubmissionsNode,
|
||||
} from '../types/Build';
|
||||
|
||||
export const BuildQuery = {
|
||||
async byIdAsync(
|
||||
@@ -76,6 +83,36 @@ export const BuildQuery = {
|
||||
|
||||
return data.builds.byId;
|
||||
},
|
||||
async withFingerprintByIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
buildId: string,
|
||||
{ useCache = true }: { useCache?: boolean } = {}
|
||||
): Promise<BuildWithFingerprintFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<BuildsWithFingerprintByIdQuery, BuildsWithFingerprintByIdQueryVariables>(
|
||||
gql`
|
||||
query BuildsWithFingerprintByIdQuery($buildId: ID!) {
|
||||
builds {
|
||||
byId(buildId: $buildId) {
|
||||
id
|
||||
...BuildWithFingerprintFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(BuildFragmentWithFingerprintNode)}
|
||||
`,
|
||||
{ buildId },
|
||||
{
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
additionalTypenames: ['Build'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data.builds.byId;
|
||||
},
|
||||
async viewBuildsOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, limit, offset, filter }: ViewBuildsOnAppQueryVariables
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { FingerprintFragmentNode } from './Fingerprint';
|
||||
import { SubmissionFragmentNode } from './Submission';
|
||||
|
||||
export const BuildFragmentNode = gql`
|
||||
@@ -72,3 +73,17 @@ export const BuildFragmentWithSubmissionsNode = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const BuildFragmentWithFingerprintNode = gql`
|
||||
${print(FingerprintFragmentNode)}
|
||||
${print(BuildFragmentNode)}
|
||||
|
||||
fragment BuildWithFingerprintFragment on Build {
|
||||
id
|
||||
...BuildFragment
|
||||
fingerprint {
|
||||
id
|
||||
...FingerprintFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,9 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const FingerprintFragmentNode = gql`
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
id
|
||||
hash
|
||||
debugInfoUrl
|
||||
}
|
||||
`;
|
||||
@@ -35,5 +35,14 @@ export const UpdateFragmentNode = gql`
|
||||
rolloutControlUpdate {
|
||||
id
|
||||
}
|
||||
fingerprint {
|
||||
id
|
||||
hash
|
||||
source {
|
||||
type
|
||||
bucketKey
|
||||
isDebugFingerprint
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
+1
-3
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
||||
|
||||
import { getProjectDashboardUrl } from '../build/utils/url';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppPrivacy, Role } from '../graphql/generated';
|
||||
import { Role } from '../graphql/generated';
|
||||
import { AppMutation } from '../graphql/mutations/AppMutation';
|
||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||
import { link } from '../log';
|
||||
@@ -21,7 +21,6 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
||||
projectInfo: {
|
||||
accountName: string;
|
||||
projectName: string;
|
||||
privacy?: AppPrivacy;
|
||||
},
|
||||
options: {
|
||||
nonInteractive: boolean;
|
||||
@@ -90,7 +89,6 @@ export async function fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsyn
|
||||
const id = await AppMutation.createAppAsync(graphqlClient, {
|
||||
accountId: account.id,
|
||||
projectName,
|
||||
privacy: projectInfo.privacy ?? AppPrivacy.Public,
|
||||
});
|
||||
spinner.succeed(`Created ${chalk.bold(projectLink)} on Expo`);
|
||||
return id;
|
||||
|
||||
@@ -7,7 +7,7 @@ import semver from 'semver';
|
||||
|
||||
import { getEASUpdateURL } from '../api';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AccountFragment, AppPrivacy } from '../graphql/generated';
|
||||
import { AccountFragment } from '../graphql/generated';
|
||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { Actor } from '../user/User';
|
||||
@@ -31,16 +31,6 @@ export function getUsername(exp: ExpoConfig, user: Actor): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export const toAppPrivacy = (privacy: ExpoConfig['privacy']): AppPrivacy => {
|
||||
if (privacy === 'public') {
|
||||
return AppPrivacy.Public;
|
||||
} else if (privacy === 'hidden') {
|
||||
return AppPrivacy.Hidden;
|
||||
} else {
|
||||
return AppPrivacy.Unlisted;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a useful name describing the project config.
|
||||
* - dynamic: app.config.js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExpoConfig, Platform as ExpoConfigPlatform } from '@expo/config';
|
||||
import { Updates } from '@expo/config-plugins';
|
||||
import { Env, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, FingerprintSource, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
@@ -12,6 +12,7 @@ import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
import promiseLimit from 'promise-limit';
|
||||
|
||||
import { maybeUploadFingerprintAsync } from './maybeUploadFingerprintAsync';
|
||||
import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync } from './projectUtils';
|
||||
import { resolveRuntimeVersionUsingCLIAsync } from './resolveRuntimeVersionAsync';
|
||||
import { selectBranchOnAppAsync } from '../branch/queries';
|
||||
@@ -47,7 +48,9 @@ import { ExpoUpdatesCLIModuleNotFoundError } from '../utils/expoUpdatesCli';
|
||||
import chunk from '../utils/expodash/chunk';
|
||||
import { truthy } from '../utils/expodash/filter';
|
||||
import groupBy from '../utils/expodash/groupBy';
|
||||
import mapMapAsync from '../utils/expodash/mapMapAsync';
|
||||
import uniqBy from '../utils/expodash/uniqBy';
|
||||
import { FingerprintOptions, createFingerprintsByKeyAsync } from '../utils/fingerprintCli';
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
// update publish does not currently support web
|
||||
@@ -631,7 +634,13 @@ export async function getBranchNameForCommandAsync({
|
||||
}
|
||||
|
||||
if (autoFlag) {
|
||||
return await getDefaultBranchNameAsync(vcsClient);
|
||||
const defaultBranchNameFromVcs = await getDefaultBranchNameAsync(vcsClient);
|
||||
if (!defaultBranchNameFromVcs) {
|
||||
throw new Error(
|
||||
'Must supply --branch or --channel for branch name as auto-detection of branch name via --auto is not supported when no VCS is present.'
|
||||
);
|
||||
}
|
||||
return defaultBranchNameFromVcs;
|
||||
} else if (nonInteractive) {
|
||||
throw new Error('Must supply --channel, --branch or --auto when in non-interactive mode.');
|
||||
} else {
|
||||
@@ -655,7 +664,7 @@ export async function getBranchNameForCommandAsync({
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
message: 'No branches found. Provide a branch name:',
|
||||
initial: await getDefaultBranchNameAsync(vcsClient),
|
||||
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
|
||||
validate: value => (value ? true : 'Branch name may not be empty.'),
|
||||
});
|
||||
branchName = name;
|
||||
@@ -728,6 +737,16 @@ export type RuntimeVersionInfo = {
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
};
|
||||
|
||||
type FingerprintInfoGroup = {
|
||||
[key in UpdatePublishPlatform]?: FingerprintInfo;
|
||||
};
|
||||
|
||||
type FingerprintInfo = {
|
||||
fingerprintHash: string;
|
||||
fingerprintSource: FingerprintSource;
|
||||
};
|
||||
|
||||
export async function getRuntimeVersionInfoObjectsAsync({
|
||||
@@ -782,6 +801,7 @@ async function getRuntimeVersionInfoForPlatformAsync({
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
}> {
|
||||
if (await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir)) {
|
||||
try {
|
||||
@@ -833,6 +853,7 @@ async function getRuntimeVersionInfoForPlatformAsync({
|
||||
return {
|
||||
runtimeVersion: resolvedRuntimeVersion,
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -858,11 +879,121 @@ export function getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersion
|
||||
runtimeVersionInfoObjects.map(
|
||||
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprint
|
||||
)[0] ?? null,
|
||||
fingerprintHash:
|
||||
runtimeVersionInfoObjects.map(
|
||||
runtimeVersionInfoObject => runtimeVersionInfoObject.runtimeVersionInfo.fingerprintHash
|
||||
)[0] ?? null,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function maybeCalculateFingerprintForRuntimeVersionInfoObjectsWithoutExpoUpdatesAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping,
|
||||
workflowsByPlatform,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping: (RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource | null;
|
||||
})[];
|
||||
workflowsByPlatform: Record<Platform, Workflow>;
|
||||
env: Env | undefined;
|
||||
}): Promise<
|
||||
(RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource | null;
|
||||
fingerprintInfoGroup: FingerprintInfoGroup;
|
||||
})[]
|
||||
> {
|
||||
const runtimesToComputeFingerprintsFor =
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.filter(
|
||||
infoGroup => !infoGroup.fingerprintHash
|
||||
);
|
||||
const fingerprintOptionsByRuntimeAndPlatform = new Map<string, FingerprintOptions>();
|
||||
for (const infoGroup of runtimesToComputeFingerprintsFor) {
|
||||
for (const platform of infoGroup.platforms) {
|
||||
const runtimeAndPlatform = `${infoGroup.runtimeVersion}-${platform}`;
|
||||
const options = {
|
||||
platforms: [platform],
|
||||
workflow: workflowsByPlatform[platform],
|
||||
projectDir,
|
||||
env,
|
||||
};
|
||||
fingerprintOptionsByRuntimeAndPlatform.set(runtimeAndPlatform, options);
|
||||
}
|
||||
}
|
||||
const fingerprintsByRuntimeAndPlatform = await createFingerprintsByKeyAsync(
|
||||
projectDir,
|
||||
fingerprintOptionsByRuntimeAndPlatform
|
||||
);
|
||||
const uploadedFingerprintsByRuntimeAndPlatform = await mapMapAsync(
|
||||
fingerprintsByRuntimeAndPlatform,
|
||||
async fingerprint => {
|
||||
return {
|
||||
...fingerprint,
|
||||
uploadedSource: (
|
||||
await maybeUploadFingerprintAsync({
|
||||
hash: fingerprint.hash,
|
||||
fingerprint: {
|
||||
fingerprintSources: fingerprint.sources,
|
||||
isDebugFingerprintSource: fingerprint.isDebugSource,
|
||||
},
|
||||
graphqlClient,
|
||||
})
|
||||
).fingerprintSource,
|
||||
};
|
||||
}
|
||||
);
|
||||
const runtimesWithComputedFingerprint = runtimesToComputeFingerprintsFor.map(runtimeInfo => {
|
||||
const fingerprintInfoGroup: FingerprintInfoGroup = {};
|
||||
for (const platform of runtimeInfo.platforms) {
|
||||
const runtimeAndPlatform = `${runtimeInfo.runtimeVersion}-${platform}`;
|
||||
const fingerprint = uploadedFingerprintsByRuntimeAndPlatform.get(runtimeAndPlatform);
|
||||
if (fingerprint && fingerprint.uploadedSource) {
|
||||
fingerprintInfoGroup[platform] = {
|
||||
fingerprintHash: fingerprint.hash,
|
||||
fingerprintSource: fingerprint.uploadedSource,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...runtimeInfo,
|
||||
fingerprintInfoGroup,
|
||||
};
|
||||
});
|
||||
|
||||
// These are runtimes whose fingerprint has already been computed and uploaded with EAS Update fingerprint runtime policy
|
||||
const runtimesWithPreviouslyComputedFingerprints =
|
||||
runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping
|
||||
.filter(
|
||||
(
|
||||
infoGroup
|
||||
): infoGroup is RuntimeVersionInfo & {
|
||||
platforms: UpdatePublishPlatform[];
|
||||
fingerprintSource: FingerprintSource;
|
||||
fingerprintHash: string;
|
||||
} => !!infoGroup.fingerprintHash && !!infoGroup.fingerprintSource
|
||||
)
|
||||
.map(infoGroup => {
|
||||
const platform = infoGroup.platforms[0];
|
||||
return {
|
||||
...infoGroup,
|
||||
fingerprintInfoGroup: {
|
||||
[platform]: {
|
||||
fingerprintHash: infoGroup.fingerprintHash,
|
||||
fingerprintSource: infoGroup.fingerprintSource,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
return [...runtimesWithComputedFingerprint, ...runtimesWithPreviouslyComputedFingerprints];
|
||||
}
|
||||
|
||||
export const platformDisplayNames: Record<UpdatePublishPlatform, string> = {
|
||||
android: 'Android',
|
||||
ios: 'iOS',
|
||||
@@ -873,21 +1004,6 @@ export const updatePublishPlatformToAppPlatform: Record<UpdatePublishPlatform, A
|
||||
ios: AppPlatform.Ios,
|
||||
};
|
||||
|
||||
const mapMapAsync = async function <K, V, M>(
|
||||
map: ReadonlyMap<K, V>,
|
||||
mapper: (value: V, key: K) => Promise<M>
|
||||
): Promise<Map<K, M>> {
|
||||
const resultingMap: Map<K, M> = new Map();
|
||||
await Promise.all(
|
||||
Array.from(map.keys()).map(async k => {
|
||||
const initialValue = map.get(k) as V;
|
||||
const result = await mapper(initialValue, k);
|
||||
resultingMap.set(k, result);
|
||||
})
|
||||
);
|
||||
return resultingMap;
|
||||
};
|
||||
|
||||
export async function getRuntimeToUpdateRolloutInfoGroupMappingAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ export async function resolveRuntimeVersionUsingCLIAsync({
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
}> {
|
||||
Log.debug('Using expo-updates runtimeversion:resolve CLI for runtime version resolution');
|
||||
|
||||
@@ -52,6 +53,9 @@ export async function resolveRuntimeVersionUsingCLIAsync({
|
||||
isDebugFingerprintSource: useDebugFingerprintSource,
|
||||
}
|
||||
: null,
|
||||
fingerprintHash: runtimeVersionResult.fingerprintSources
|
||||
? runtimeVersionResult.runtimeVersion
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +79,7 @@ export async function resolveRuntimeVersionAsync({
|
||||
fingerprintSources: object[];
|
||||
isDebugFingerprintSource: boolean;
|
||||
} | null;
|
||||
fingerprintHash: string | null;
|
||||
} | null> {
|
||||
if (!(await isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync(projectDir))) {
|
||||
// fall back to the previous behavior (using the @expo/config-plugins eas-cli dependency rather
|
||||
@@ -82,6 +87,7 @@ export async function resolveRuntimeVersionAsync({
|
||||
return {
|
||||
runtimeVersion: await Updates.getRuntimeVersionNullableAsync(projectDir, exp, platform),
|
||||
fingerprint: null,
|
||||
fingerprintHash: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
return newChannelInfo;
|
||||
}
|
||||
|
||||
async confirmCreationAsync(ctx: EASUpdateContext): Promise<boolean> {
|
||||
private async confirmCreationAsync(ctx: EASUpdateContext): Promise<boolean> {
|
||||
const { nonInteractive } = ctx;
|
||||
if (nonInteractive) {
|
||||
return true;
|
||||
@@ -159,7 +159,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
});
|
||||
}
|
||||
|
||||
async getChannelObjectAsync(
|
||||
private async getChannelObjectAsync(
|
||||
ctx: EASUpdateContext,
|
||||
runtimeVersion: string
|
||||
): Promise<UpdateChannelObject> {
|
||||
@@ -172,7 +172,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
});
|
||||
}
|
||||
|
||||
async getLatestUpdateGroupOnBranchAsync(
|
||||
private async getLatestUpdateGroupOnBranchAsync(
|
||||
ctx: EASUpdateContext,
|
||||
branchInfo: UpdateBranchBasicInfoFragment,
|
||||
runtimeVersion: string
|
||||
@@ -195,7 +195,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
return updateGroups[0] ?? null;
|
||||
}
|
||||
|
||||
async selectRuntimeVersionAsync(
|
||||
private async selectRuntimeVersionAsync(
|
||||
ctx: EASUpdateContext,
|
||||
branchToRollout: UpdateBranchBasicInfoFragment,
|
||||
defaultBranchId: string
|
||||
@@ -223,7 +223,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
);
|
||||
}
|
||||
|
||||
async selectRuntimeVersionFromAlternativeSourceAsync(
|
||||
private async selectRuntimeVersionFromAlternativeSourceAsync(
|
||||
ctx: EASUpdateContext,
|
||||
branchToRollout: UpdateBranchBasicInfoFragment,
|
||||
defaultBranch: UpdateBranchBasicInfoFragment
|
||||
@@ -271,7 +271,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
);
|
||||
}
|
||||
|
||||
async selectRuntimeVersionFromProjectConfigAsync(ctx: EASUpdateContext): Promise<string> {
|
||||
private async selectRuntimeVersionFromProjectConfigAsync(ctx: EASUpdateContext): Promise<string> {
|
||||
const platforms: ('ios' | 'android')[] = ['ios', 'android'];
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(ctx.app.projectDir, ctx.vcsClient);
|
||||
const runtimes = (
|
||||
@@ -313,7 +313,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
return selectedRuntime;
|
||||
}
|
||||
|
||||
async selectBranchAsync(
|
||||
private async selectBranchAsync(
|
||||
ctx: EASUpdateContext,
|
||||
defaultBranchId: string
|
||||
): Promise<UpdateBranchBasicInfoFragment> {
|
||||
@@ -333,7 +333,7 @@ export class CreateRollout implements EASUpdateAction<UpdateChannelBasicInfoFrag
|
||||
return branchInfo;
|
||||
}
|
||||
|
||||
async resolveBranchNameAsync(
|
||||
private async resolveBranchNameAsync(
|
||||
ctx: EASUpdateContext,
|
||||
branchName: string
|
||||
): Promise<UpdateBranchBasicInfoFragment> {
|
||||
|
||||
@@ -86,7 +86,7 @@ export class EditRollout implements EASUpdateAction<UpdateChannelBasicInfoFragme
|
||||
return newChannelInfo;
|
||||
}
|
||||
|
||||
async confirmEditAsync(ctx: EASUpdateContext): Promise<boolean> {
|
||||
private async confirmEditAsync(ctx: EASUpdateContext): Promise<boolean> {
|
||||
const { nonInteractive } = ctx;
|
||||
if (nonInteractive) {
|
||||
return true;
|
||||
@@ -96,7 +96,7 @@ export class EditRollout implements EASUpdateAction<UpdateChannelBasicInfoFragme
|
||||
});
|
||||
}
|
||||
|
||||
async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
private async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
const { graphqlClient, app } = ctx;
|
||||
const { projectId } = app;
|
||||
if (!isRollout(this.channelInfo)) {
|
||||
|
||||
@@ -87,7 +87,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
|
||||
return await this.performOutcomeAsync(ctx, rollout, outcome);
|
||||
}
|
||||
|
||||
async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
private async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
const { graphqlClient, app } = ctx;
|
||||
const { projectId } = app;
|
||||
if (!isRollout(this.channelInfo)) {
|
||||
@@ -107,7 +107,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
|
||||
});
|
||||
}
|
||||
|
||||
async selectOutcomeAsync(rollout: Rollout<UpdateBranchObject>): Promise<EndOutcome> {
|
||||
private async selectOutcomeAsync(rollout: Rollout<UpdateBranchObject>): Promise<EndOutcome> {
|
||||
const { rolledOutBranch, percentRolledOut, defaultBranch } = rollout;
|
||||
const rolledOutUpdateGroup = rolledOutBranch.updateGroups[0];
|
||||
const defaultUpdateGroup = defaultBranch.updateGroups[0];
|
||||
@@ -144,7 +144,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
|
||||
return selectedOutcome;
|
||||
}
|
||||
|
||||
async performOutcomeAsync(
|
||||
private async performOutcomeAsync(
|
||||
ctx: EASUpdateContext,
|
||||
rollout: Rollout<UpdateBranchObject>,
|
||||
outcome: EndOutcome
|
||||
@@ -185,7 +185,7 @@ export class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragmen
|
||||
return newChannelInfo;
|
||||
}
|
||||
|
||||
async confirmOutcomeAsync(
|
||||
private async confirmOutcomeAsync(
|
||||
ctx: EASUpdateContext,
|
||||
selectedOutcome: EndOutcome,
|
||||
rollout: Rollout<UpdateBranchObject>
|
||||
|
||||
@@ -61,7 +61,7 @@ export class ManageRollout implements EASUpdateAction<EASUpdateAction> {
|
||||
}
|
||||
}
|
||||
|
||||
async selectActionAsync(): Promise<ManageRolloutActions> {
|
||||
private async selectActionAsync(): Promise<ManageRolloutActions> {
|
||||
const manageOptions = [ManageRolloutActions.EDIT, ManageRolloutActions.END];
|
||||
if (this.options.callingAction) {
|
||||
manageOptions.push(ManageRolloutActions.GO_BACK);
|
||||
@@ -78,7 +78,7 @@ export class ManageRollout implements EASUpdateAction<EASUpdateAction> {
|
||||
return selectedManageOption;
|
||||
}
|
||||
|
||||
async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
private async getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject> {
|
||||
const { graphqlClient, app } = ctx;
|
||||
const { projectId } = app;
|
||||
if (!isRollout(this.channelInfo)) {
|
||||
|
||||
@@ -91,7 +91,7 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async runActionAsync(
|
||||
private async runActionAsync(
|
||||
ctx: EASUpdateContext,
|
||||
action: RolloutActions,
|
||||
channelObject: UpdateChannelObject
|
||||
@@ -108,7 +108,7 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
}
|
||||
}
|
||||
|
||||
viewRollout(channelObject: UpdateChannelObject): UpdateChannelObject {
|
||||
private viewRollout(channelObject: UpdateChannelObject): UpdateChannelObject {
|
||||
if (!this.options.json) {
|
||||
printRollout(channelObject);
|
||||
Log.warn('For formatted output, add the --json flag to your command.');
|
||||
@@ -116,7 +116,7 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
return channelObject;
|
||||
}
|
||||
|
||||
async getJsonAsync({
|
||||
private async getJsonAsync({
|
||||
originalChannelObject,
|
||||
updatedChannelObject,
|
||||
}: {
|
||||
@@ -134,7 +134,9 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
};
|
||||
}
|
||||
|
||||
async getRolloutJsonAsync(channelObject: UpdateChannelObject): Promise<JSONRolloutOutput> {
|
||||
private async getRolloutJsonAsync(
|
||||
channelObject: UpdateChannelObject
|
||||
): Promise<JSONRolloutOutput> {
|
||||
const rollout = getRollout(channelObject);
|
||||
return {
|
||||
defaultBranch: rollout.defaultBranch,
|
||||
@@ -145,7 +147,7 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
};
|
||||
}
|
||||
|
||||
getRuntimeVersion(channelInfo: UpdateChannelBasicInfoFragment): string | undefined {
|
||||
private getRuntimeVersion(channelInfo: UpdateChannelBasicInfoFragment): string | undefined {
|
||||
if (isRollout(channelInfo)) {
|
||||
const updatedRolloutInfo = getRolloutInfo(channelInfo);
|
||||
if (isConstrainedRolloutInfo(updatedRolloutInfo)) {
|
||||
@@ -155,7 +157,7 @@ export class NonInteractiveRollout implements EASUpdateAction<void> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getChannelObjectAsync(
|
||||
private async getChannelObjectAsync(
|
||||
ctx: EASUpdateContext,
|
||||
{ channelName, runtimeVersion }: { channelName: string; runtimeVersion?: string }
|
||||
): Promise<UpdateChannelObject> {
|
||||
|
||||
@@ -54,7 +54,7 @@ export class RolloutMainMenu implements EASUpdateAction<void> {
|
||||
await this.runActionAsync(ctx, menuOption);
|
||||
}
|
||||
|
||||
async runActionAsync(ctx: EASUpdateContext, menuAction: MainMenuActions): Promise<null> {
|
||||
private async runActionAsync(ctx: EASUpdateContext, menuAction: MainMenuActions): Promise<null> {
|
||||
const { channelName } = this.options;
|
||||
switch (menuAction) {
|
||||
case MainMenuActions.CREATE_NEW: {
|
||||
@@ -87,13 +87,15 @@ export class RolloutMainMenu implements EASUpdateAction<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async selectRolloutAsync(ctx: EASUpdateContext): Promise<UpdateChannelBasicInfoFragment | null> {
|
||||
private async selectRolloutAsync(
|
||||
ctx: EASUpdateContext
|
||||
): Promise<UpdateChannelBasicInfoFragment | null> {
|
||||
const selectRollout = new SelectRollout();
|
||||
const channelInfo = await selectRollout.runAsync(ctx);
|
||||
return channelInfo;
|
||||
}
|
||||
|
||||
async selectChannelToRolloutAsync(
|
||||
private async selectChannelToRolloutAsync(
|
||||
ctx: EASUpdateContext
|
||||
): Promise<UpdateChannelBasicInfoFragment> {
|
||||
let hasSomeChannel = false;
|
||||
@@ -113,7 +115,7 @@ export class RolloutMainMenu implements EASUpdateAction<void> {
|
||||
return channelInfo;
|
||||
}
|
||||
|
||||
async resolveChannelNameAsync(
|
||||
private async resolveChannelNameAsync(
|
||||
ctx: EASUpdateContext,
|
||||
channelName: string
|
||||
): Promise<UpdateChannelBasicInfoFragment> {
|
||||
@@ -124,7 +126,7 @@ export class RolloutMainMenu implements EASUpdateAction<void> {
|
||||
});
|
||||
}
|
||||
|
||||
toMainMenuAction(action: RolloutActions): MainMenuActions {
|
||||
private toMainMenuAction(action: RolloutActions): MainMenuActions {
|
||||
if (action === MainMenuActions.CREATE_NEW) {
|
||||
return MainMenuActions.CREATE_NEW;
|
||||
} else if (
|
||||
@@ -138,7 +140,7 @@ export class RolloutMainMenu implements EASUpdateAction<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async promptMenuActionAsync(): Promise<MainMenuActions> {
|
||||
private async promptMenuActionAsync(): Promise<MainMenuActions> {
|
||||
const menuOptions = [MainMenuActions.CREATE_NEW, MainMenuActions.MANAGE_EXISTING];
|
||||
const { menuOption: selectedMenuOption } = await promptAsync({
|
||||
type: 'select',
|
||||
|
||||
@@ -27,7 +27,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
|
||||
: `runtime`;
|
||||
}
|
||||
|
||||
warnNoRuntime(): void {
|
||||
private warnNoRuntime(): void {
|
||||
if (this.options.anotherBranchToIntersectRuntimesBy) {
|
||||
const intersectBranchName = this.options.anotherBranchToIntersectRuntimesBy.name;
|
||||
Log.warn(
|
||||
@@ -42,7 +42,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
formatCantFindRuntime(): string {
|
||||
private formatCantFindRuntime(): string {
|
||||
return `🕵️ Not finding the update you were looking for? ${learnMore(
|
||||
'https://expo.fyi/eas-update-rollouts'
|
||||
)}`;
|
||||
@@ -104,7 +104,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
|
||||
return selectedRuntime.version;
|
||||
}
|
||||
|
||||
async getNewestRuntimeAsync(
|
||||
private async getNewestRuntimeAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
@@ -126,7 +126,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
|
||||
});
|
||||
}
|
||||
|
||||
async displayLatestUpdateGroupAsync({
|
||||
private async displayLatestUpdateGroupAsync({
|
||||
graphqlClient,
|
||||
appId,
|
||||
branchName,
|
||||
@@ -153,7 +153,7 @@ export class SelectRuntime implements EASUpdateAction<string | null> {
|
||||
return formatRuntimeWithUpdateGroup(updateGroups[0], runtime, branchName);
|
||||
}
|
||||
|
||||
async selectRuntimesAsync(
|
||||
private async selectRuntimesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonAccessor } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
|
||||
import { syncUpdatesConfigurationAsync as syncAndroidUpdatesConfigurationAsync } from './android/UpdatesModule';
|
||||
@@ -149,7 +150,7 @@ async function ensureEASUpdatesIsConfiguredInExpoConfigAsync({
|
||||
return {
|
||||
projectChanged: true,
|
||||
// TODO(cedric): fix return type of `modifyConfigAsync` to avoid `null` for type === success repsonses
|
||||
exp: result.config?.expo!,
|
||||
exp: nullthrows(result.config, 'Expected config to be defined'),
|
||||
};
|
||||
|
||||
case 'warn':
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ export async function getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync(
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName: channelName,
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export async function getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync(
|
||||
} = await createChannelOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
channelName,
|
||||
branchId,
|
||||
branchId: branch.id,
|
||||
});
|
||||
|
||||
if (!newChannel) {
|
||||
@@ -52,7 +52,7 @@ export async function getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync(
|
||||
);
|
||||
}
|
||||
|
||||
branchInfo = { branchId, branchName: channelName };
|
||||
branchInfo = { branchId: branch.id, branchName: channelName };
|
||||
}
|
||||
|
||||
return branchInfo;
|
||||
|
||||
@@ -118,6 +118,27 @@ export async function republishAsync({
|
||||
updateInfoGroup: Object.fromEntries(
|
||||
updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)])
|
||||
),
|
||||
fingerprintInfoGroup: Object.fromEntries(
|
||||
updatesToPublish.map(update => {
|
||||
const fingerprint = update.fingerprint;
|
||||
if (!fingerprint) {
|
||||
return [update.platform, undefined];
|
||||
}
|
||||
return [
|
||||
update.platform,
|
||||
{
|
||||
fingerprintHash: fingerprint.hash,
|
||||
fingerprintSource: fingerprint.source
|
||||
? {
|
||||
type: fingerprint.source.type,
|
||||
bucketKey: fingerprint.source.bucketKey,
|
||||
isDebugFingerprint: fingerprint.source.isDebugFingerprint,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
updatesRepublished = await PublishMutation.publishUpdateGroupAsync(graphqlClient, [
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import Log from '../../log';
|
||||
import { abridgedDiff } from '../fingerprintDiff';
|
||||
|
||||
jest.mock('chalk', () => ({
|
||||
red: jest.fn(text => `red(${text})`),
|
||||
green: jest.fn(text => `green(${text})`),
|
||||
gray: jest.fn(text => `gray(${text})`),
|
||||
cyan: jest.fn(text => `cyan(${text})`),
|
||||
}));
|
||||
|
||||
jest.spyOn(Log, 'log').mockImplementation(jest.fn());
|
||||
|
||||
describe('abridgedDiff', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should output the diff for added and removed lines with context', () => {
|
||||
const str1 = 'Line1\nLine2\nLine3';
|
||||
const str2 = 'Line1\nLineX\nLine3';
|
||||
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = [
|
||||
'cyan(@@ -2,1 +2,1 @@)',
|
||||
' gray(Line1)',
|
||||
'red(-Line2)',
|
||||
'green(+LineX)',
|
||||
' gray(Line3)',
|
||||
].join('\n');
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
|
||||
it('should output the diff for completely different strings', () => {
|
||||
const str1 = 'LineA\nLineB';
|
||||
const str2 = 'LineC\nLineD';
|
||||
|
||||
abridgedDiff(str1, str2, 0);
|
||||
|
||||
const expectedOutput = [
|
||||
'cyan(@@ -1,2 +1,2 @@)',
|
||||
'red(-LineA)',
|
||||
'red(-LineB)',
|
||||
'green(+LineC)',
|
||||
'green(+LineD)',
|
||||
].join('\n');
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
|
||||
it('should not include context lines if contextLines is 0', () => {
|
||||
const str1 = 'Line1\nLine2\nLine3';
|
||||
const str2 = 'Line1\nLineX\nLine3';
|
||||
|
||||
abridgedDiff(str1, str2, 0);
|
||||
|
||||
const expectedOutput = ['cyan(@@ -2,1 +2,1 @@)', 'red(-Line2)', 'green(+LineX)'].join('\n');
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
|
||||
it('should handle strings with no differences', () => {
|
||||
const str1 = 'SameLine1\nSameLine2';
|
||||
const str2 = 'SameLine1\nSameLine2';
|
||||
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
it('should handle empty input strings', () => {
|
||||
const str1 = '';
|
||||
const str2 = 'NewLine1\nNewLine2';
|
||||
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = ['cyan(@@ -1,0 +1,2 @@)', 'green(+NewLine1)', 'green(+NewLine2)'].join(
|
||||
'\n'
|
||||
);
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
|
||||
it('should handle strings with only removed lines', () => {
|
||||
const str1 = 'OldLine1\nOldLine2';
|
||||
const str2 = '';
|
||||
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = ['cyan(@@ -1,2 +1,0 @@)', 'red(-OldLine1)', 'red(-OldLine2)'].join('\n');
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
|
||||
it('should handle strings with mixed changes and context lines', () => {
|
||||
const str1 = 'Header\nLine1\nLine2\nFooter';
|
||||
const str2 = 'Header\nLine1\nLineX\nFooter';
|
||||
|
||||
abridgedDiff(str1, str2, 1);
|
||||
|
||||
const expectedOutput = [
|
||||
'cyan(@@ -3,1 +3,1 @@)',
|
||||
' gray(Line1)',
|
||||
'red(-Line2)',
|
||||
'green(+LineX)',
|
||||
' gray(Footer)',
|
||||
].join('\n');
|
||||
|
||||
expect(Log.log).toHaveBeenCalledTimes(1);
|
||||
expect(Log.log).toHaveBeenCalledWith(expectedOutput);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
export default async function mapMapAsync<K, V, M>(
|
||||
map: ReadonlyMap<K, V>,
|
||||
mapper: (value: V, key: K) => Promise<M>
|
||||
): Promise<Map<K, M>> {
|
||||
const resultingMap: Map<K, M> = new Map();
|
||||
await Promise.all(
|
||||
Array.from(map.keys()).map(async k => {
|
||||
const initialValue = map.get(k) as V;
|
||||
const result = await mapper(initialValue, k);
|
||||
resultingMap.set(k, result);
|
||||
})
|
||||
);
|
||||
return resultingMap;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* DO NOT EDIT unless the same change is made in `@expo/fingerprint`
|
||||
*/
|
||||
|
||||
export interface HashSourceFile {
|
||||
type: 'file';
|
||||
filePath: string;
|
||||
|
||||
/**
|
||||
* Reasons of this source coming from
|
||||
*/
|
||||
reasons: string[];
|
||||
}
|
||||
|
||||
export interface HashSourceDir {
|
||||
type: 'dir';
|
||||
filePath: string;
|
||||
|
||||
/**
|
||||
* Reasons of this source coming from
|
||||
*/
|
||||
reasons: string[];
|
||||
}
|
||||
|
||||
export interface HashSourceContents {
|
||||
type: 'contents';
|
||||
id: string;
|
||||
contents: string | Buffer;
|
||||
|
||||
/**
|
||||
* Reasons of this source coming from
|
||||
*/
|
||||
reasons: string[];
|
||||
}
|
||||
|
||||
export type HashSource = HashSourceFile | HashSourceDir | HashSourceContents;
|
||||
|
||||
export interface Fingerprint {
|
||||
/**
|
||||
* Sources and their hash values to generate a fingerprint
|
||||
*/
|
||||
sources: FingerprintSource[];
|
||||
|
||||
/**
|
||||
* The final hash value of the whole fingerprint
|
||||
*/
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface DebugInfoFile {
|
||||
path: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface DebugInfoDir {
|
||||
path: string;
|
||||
hash: string;
|
||||
children: (DebugInfoFile | DebugInfoDir | undefined)[];
|
||||
}
|
||||
|
||||
export interface DebugInfoContents {
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export type DebugInfo = DebugInfoFile | DebugInfoDir | DebugInfoContents;
|
||||
|
||||
export type FingerprintSource = HashSource & {
|
||||
/**
|
||||
* Hash value of the `source`.
|
||||
* If the source is excluding by `Options.dirExcludes`, the value will be null.
|
||||
*/
|
||||
hash: string | null;
|
||||
/**
|
||||
* Debug info from the hashing process. Differs based on source type. Designed to be consumed by humans
|
||||
* as opposed to programmatically.
|
||||
*/
|
||||
debugInfo?: DebugInfo;
|
||||
};
|
||||
|
||||
export type FingerprintDiffItem =
|
||||
| {
|
||||
/**
|
||||
* The operation type of the diff item.
|
||||
*/
|
||||
op: 'added';
|
||||
/**
|
||||
* The added source.
|
||||
*/
|
||||
addedSource: FingerprintSource;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* The operation type of the diff item.
|
||||
*/
|
||||
op: 'removed';
|
||||
/**
|
||||
* The removed source.
|
||||
*/
|
||||
removedSource: FingerprintSource;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* The operation type of the diff item.
|
||||
*/
|
||||
op: 'changed';
|
||||
/**
|
||||
* The source before.
|
||||
*/
|
||||
beforeSource: FingerprintSource;
|
||||
/**
|
||||
* The source after.
|
||||
*/
|
||||
afterSource: FingerprintSource;
|
||||
};
|
||||
@@ -1,23 +1,43 @@
|
||||
import { Env, Workflow } from '@expo/eas-build-job';
|
||||
import { silent as silentResolveFrom } from 'resolve-from';
|
||||
|
||||
import mapMapAsync from './expodash/mapMapAsync';
|
||||
import { Fingerprint, FingerprintDiffItem } from './fingerprint';
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
export type FingerprintOptions = {
|
||||
workflow: Workflow;
|
||||
platforms: string[];
|
||||
debug?: boolean;
|
||||
env: Env | undefined;
|
||||
cwd?: string;
|
||||
};
|
||||
|
||||
export function diffFingerprint(
|
||||
projectDir: string,
|
||||
fingerprint1: Fingerprint,
|
||||
fingerprint2: Fingerprint
|
||||
): FingerprintDiffItem[] | null {
|
||||
// @expo/fingerprint is exported in the expo package for SDK 52+
|
||||
const fingerprintPath = silentResolveFrom(projectDir, 'expo/fingerprint');
|
||||
if (!fingerprintPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Fingerprint = require(fingerprintPath);
|
||||
return Fingerprint.diffFingerprints(fingerprint1, fingerprint2);
|
||||
}
|
||||
|
||||
export async function createFingerprintAsync(
|
||||
projectDir: string,
|
||||
options: {
|
||||
workflow: Workflow;
|
||||
platform: string;
|
||||
debug?: boolean;
|
||||
env: Env | undefined;
|
||||
cwd?: string;
|
||||
}
|
||||
): Promise<{
|
||||
hash: string;
|
||||
sources: object[];
|
||||
isDebugSource: boolean;
|
||||
} | null> {
|
||||
options: FingerprintOptions
|
||||
): Promise<
|
||||
| (Fingerprint & {
|
||||
isDebugSource: boolean;
|
||||
})
|
||||
| null
|
||||
> {
|
||||
// @expo/fingerprint is exported in the expo package for SDK 52+
|
||||
const fingerprintPath = silentResolveFrom(projectDir, 'expo/fingerprint');
|
||||
if (!fingerprintPath) {
|
||||
@@ -36,18 +56,11 @@ export async function createFingerprintAsync(
|
||||
|
||||
const spinner = ora(`Computing project fingerprint`).start();
|
||||
try {
|
||||
const Fingerprint = require(fingerprintPath);
|
||||
const fingerprintOptions: Record<string, any> = {};
|
||||
if (options.platform) {
|
||||
fingerprintOptions.platforms = [options.platform];
|
||||
}
|
||||
if (options.workflow === Workflow.MANAGED) {
|
||||
fingerprintOptions.ignorePaths = ['android/**/*', 'ios/**/*'];
|
||||
}
|
||||
if (options.debug) {
|
||||
fingerprintOptions.debug = true;
|
||||
}
|
||||
const fingerprint = await Fingerprint.createFingerprintAsync(projectDir, fingerprintOptions);
|
||||
const fingerprint = await createFingerprintWithoutLoggingAsync(
|
||||
projectDir,
|
||||
fingerprintPath,
|
||||
options
|
||||
);
|
||||
spinner.succeed(`Computed project fingerprint`);
|
||||
return fingerprint;
|
||||
} catch (e) {
|
||||
@@ -60,3 +73,86 @@ export async function createFingerprintAsync(
|
||||
spinner.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async function createFingerprintWithoutLoggingAsync(
|
||||
projectDir: string,
|
||||
fingerprintPath: string,
|
||||
options: FingerprintOptions
|
||||
): Promise<
|
||||
Fingerprint & {
|
||||
isDebugSource: boolean;
|
||||
}
|
||||
> {
|
||||
const Fingerprint = require(fingerprintPath);
|
||||
const fingerprintOptions: Record<string, any> = {};
|
||||
if (options.platforms) {
|
||||
fingerprintOptions.platforms = [...options.platforms];
|
||||
}
|
||||
if (options.workflow === Workflow.MANAGED) {
|
||||
fingerprintOptions.ignorePaths = ['android/**/*', 'ios/**/*'];
|
||||
}
|
||||
if (options.debug) {
|
||||
fingerprintOptions.debug = true;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/return-await
|
||||
return await Fingerprint.createFingerprintAsync(projectDir, fingerprintOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes project fingerprints based on provided options and returns a map of fingerprint data keyed by a string.
|
||||
*
|
||||
* @param projectDir - The root directory of the project.
|
||||
* @param fingerprintOptionsByKey - A map where each key is associated with options for generating the fingerprint.
|
||||
* - **Key**: A unique identifier (`string`) for the fingerprint options.
|
||||
* - **Value**: An object containing options for generating a fingerprint.
|
||||
*
|
||||
* @returns A promise that resolves to a map where each key corresponds to the input keys, and each value is an object containing fingerprint data.
|
||||
*
|
||||
* @throws Will throw an error if fingerprint computation fails.
|
||||
*/
|
||||
export async function createFingerprintsByKeyAsync(
|
||||
projectDir: string,
|
||||
fingerprintOptionsByKey: Map<string, FingerprintOptions>
|
||||
): Promise<
|
||||
Map<
|
||||
string,
|
||||
Fingerprint & {
|
||||
isDebugSource: boolean;
|
||||
}
|
||||
>
|
||||
> {
|
||||
// @expo/fingerprint is exported in the expo package for SDK 52+
|
||||
const fingerprintPath = silentResolveFrom(projectDir, 'expo/fingerprint');
|
||||
if (!fingerprintPath) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
if (process.env.EAS_SKIP_AUTO_FINGERPRINT) {
|
||||
Log.log('Skipping project fingerprints');
|
||||
return new Map();
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
Log.log('⌛️ Computing the project fingerprints is taking longer than expected...');
|
||||
Log.log('⏩ To skip this step, set the environment variable: EAS_SKIP_AUTO_FINGERPRINT=1');
|
||||
}, 5000);
|
||||
|
||||
const spinner = ora(`Computing project fingerprints`).start();
|
||||
try {
|
||||
const fingerprintsByKey = await mapMapAsync(
|
||||
fingerprintOptionsByKey,
|
||||
async options =>
|
||||
await createFingerprintWithoutLoggingAsync(projectDir, fingerprintPath, options)
|
||||
);
|
||||
spinner.succeed(`Computed project fingerprints`);
|
||||
return fingerprintsByKey;
|
||||
} catch (e) {
|
||||
spinner.fail(`Failed to compute project fingerprints`);
|
||||
Log.log('⏩ To skip this step, set the environment variable: EAS_SKIP_AUTO_FINGERPRINT=1');
|
||||
throw e;
|
||||
} finally {
|
||||
// Clear the timeout if the operation finishes before the time limit
|
||||
clearTimeout(timeoutId);
|
||||
spinner.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import chalk from 'chalk';
|
||||
import { diffLines } from 'diff';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
/**
|
||||
* Computes and prints a line-based diff between two strings, displaying changes as chunks.
|
||||
*
|
||||
* Each chunk contains lines that were added or removed, prefixed with contextual lines
|
||||
* from the unchanged parts of the strings. The output is styled similarly to `git diff`
|
||||
* with headers indicating line ranges for the changes in the original and modified strings.
|
||||
*
|
||||
* @param {string} str1 - The original string to compare.
|
||||
* @param {string} str2 - The modified string to compare against the original.
|
||||
* @param {number} [contextLines=2] - The number of unchanged lines to display before and after each chunk of changes.
|
||||
*
|
||||
* ### Output:
|
||||
* - Each chunk begins with a header in the format `@@ -<original range> +<modified range> @@`.
|
||||
* - Removed lines are prefixed with a red `-` and the original line number.
|
||||
* - Added lines are prefixed with a green `+` and the modified line number.
|
||||
* - Context lines are displayed in gray without a `+` or `-` prefix.
|
||||
*
|
||||
* ### Notes:
|
||||
* - Consecutive changes are grouped into a single chunk if separated by no more than the specified number of context lines.
|
||||
*
|
||||
* ### Example:
|
||||
* ```typescript
|
||||
* abridgedDiff("Line1\nLine2\nLine3", "Line1\nLineX\nLine3", 1);
|
||||
*
|
||||
* Output:
|
||||
* `@@ -2,1 +2,1 @@`
|
||||
* Line1
|
||||
* -Line2
|
||||
* +LineX
|
||||
* Line3
|
||||
* ```
|
||||
*/
|
||||
export function abridgedDiff(str1: string, str2: string, contextLines: number = 2): void {
|
||||
const changes = diffLines(str1, str2);
|
||||
|
||||
const output: string[] = [];
|
||||
let lineNumberOriginal = 1;
|
||||
let lineNumberModified = 1;
|
||||
|
||||
let currentChunk: string[] = [];
|
||||
let currentChunkPriorContext: string[] = [];
|
||||
let currentChunkAfterContext: string[] = [];
|
||||
let startOriginal: number | null = null; // Start line in the original for the current chunk
|
||||
let startModified: number | null = null; // Start line in the modified for the current chunk
|
||||
let addedLines = 0;
|
||||
let removedLines = 0;
|
||||
|
||||
const flushChunk = (): void => {
|
||||
if (currentChunk.length > 0) {
|
||||
const originalRange = `${startOriginal},${removedLines || 0}`;
|
||||
const modifiedRange = `${startModified},${addedLines || 0}`;
|
||||
// `git diff` style header
|
||||
output.push(chalk.cyan(`@@ -${originalRange} +${modifiedRange} @@`));
|
||||
output.push(...currentChunkPriorContext);
|
||||
output.push(...currentChunk);
|
||||
output.push(...currentChunkAfterContext);
|
||||
currentChunk = [];
|
||||
currentChunkPriorContext = [];
|
||||
currentChunkAfterContext = [];
|
||||
addedLines = 0;
|
||||
removedLines = 0;
|
||||
}
|
||||
};
|
||||
|
||||
for (const change of changes) {
|
||||
const lines = change.value.split('\n').filter(line => line);
|
||||
|
||||
if (change.added || change.removed) {
|
||||
// Initialize start lines for the chunk if not already set
|
||||
if (startOriginal === null) {
|
||||
startOriginal = lineNumberOriginal;
|
||||
}
|
||||
if (startModified === null) {
|
||||
startModified = lineNumberModified;
|
||||
}
|
||||
|
||||
if (change.removed) {
|
||||
lines.forEach(line => {
|
||||
currentChunk.push(`${chalk.red(`-${line}`)}`);
|
||||
lineNumberOriginal++;
|
||||
removedLines++;
|
||||
});
|
||||
}
|
||||
if (change.added) {
|
||||
lines.forEach(line => {
|
||||
currentChunk.push(`${chalk.green(`+${line}`)}`);
|
||||
lineNumberModified++;
|
||||
addedLines++;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Unchanged lines (context)
|
||||
lines.forEach((line, i) => {
|
||||
if (currentChunk.length > 0) {
|
||||
// Add leading context after a change
|
||||
if (i < contextLines) {
|
||||
currentChunkAfterContext.push(` ${chalk.gray(line)}`);
|
||||
}
|
||||
} else {
|
||||
// Add trailing context before a change
|
||||
if (lines.length - 1 - contextLines < i) {
|
||||
currentChunkPriorContext.push(` ${chalk.gray(line)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const isFinalLineOfAfterContext = i === contextLines - 1 || i === lines.length - 1;
|
||||
|
||||
if (currentChunk.length > 0 && isFinalLineOfAfterContext) {
|
||||
flushChunk();
|
||||
}
|
||||
lineNumberOriginal++;
|
||||
lineNumberModified++;
|
||||
});
|
||||
|
||||
startOriginal = null;
|
||||
startModified = null;
|
||||
}
|
||||
}
|
||||
|
||||
flushChunk(); // Flush any remaining chunk
|
||||
Log.log(output.join('\n'));
|
||||
}
|
||||
@@ -32,14 +32,14 @@ export async function promptVariableTypeAsync(
|
||||
}
|
||||
|
||||
export function parseVisibility(
|
||||
stringVisibility: 'plaintext' | 'sensitive' | 'encrypted'
|
||||
stringVisibility: 'plaintext' | 'sensitive' | 'secret'
|
||||
): EnvironmentVariableVisibility {
|
||||
switch (stringVisibility) {
|
||||
case 'plaintext':
|
||||
return EnvironmentVariableVisibility.Public;
|
||||
case 'sensitive':
|
||||
return EnvironmentVariableVisibility.Sensitive;
|
||||
case 'encrypted':
|
||||
case 'secret':
|
||||
return EnvironmentVariableVisibility.Secret;
|
||||
default:
|
||||
throw new Error(`Invalid visibility: ${stringVisibility}`);
|
||||
@@ -67,7 +67,7 @@ export async function promptVariableVisibilityAsync(
|
||||
selected: selectedVisibility === EnvironmentVariableVisibility.Sensitive,
|
||||
},
|
||||
{
|
||||
title: 'Encrypted',
|
||||
title: 'Secret',
|
||||
value: EnvironmentVariableVisibility.Secret,
|
||||
selected: selectedVisibility === EnvironmentVariableVisibility.Secret,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { CombinedError } from '@urql/core';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { getProjectGitHubSettingsUrl } from '../build/utils/url';
|
||||
import { WorkflowRevisionMutation } from '../graphql/mutations/WorkflowRevisionMutation';
|
||||
import Log, { link } from '../log';
|
||||
|
||||
export namespace WorkflowFile {
|
||||
export async function readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath,
|
||||
}: {
|
||||
projectDir: string;
|
||||
filePath: string;
|
||||
}): Promise<{ yamlConfig: string; filePath: string }> {
|
||||
const [yamlFromEasWorkflowsFile, yamlFromFile] = await Promise.allSettled([
|
||||
fs.promises.readFile(path.join(projectDir, '.eas', 'workflows', filePath), 'utf8'),
|
||||
fs.promises.readFile(path.join(process.cwd(), filePath), 'utf8'),
|
||||
]);
|
||||
|
||||
// We prioritize .eas/workflows/${file} over ${file}, because
|
||||
// in the worst case we'll try to read .eas/workflows/.eas/workflows/test.yml,
|
||||
// which is likely not to exist.
|
||||
if (yamlFromEasWorkflowsFile.status === 'fulfilled') {
|
||||
return {
|
||||
yamlConfig: yamlFromEasWorkflowsFile.value,
|
||||
filePath: path.join(projectDir, '.eas', 'workflows', filePath),
|
||||
};
|
||||
} else if (yamlFromFile.status === 'fulfilled') {
|
||||
return {
|
||||
yamlConfig: yamlFromFile.value,
|
||||
filePath: path.join(process.cwd(), filePath),
|
||||
};
|
||||
}
|
||||
|
||||
throw yamlFromFile.reason;
|
||||
}
|
||||
|
||||
export function maybePrintWorkflowFileValidationErrors({
|
||||
error,
|
||||
accountName,
|
||||
projectName,
|
||||
}: {
|
||||
error: CombinedError;
|
||||
accountName: string;
|
||||
projectName: string;
|
||||
}): void {
|
||||
const validationErrors = error.graphQLErrors.flatMap(e => {
|
||||
return WorkflowRevisionMutation.ValidationErrorExtensionZ.safeParse(e.extensions).data ?? [];
|
||||
});
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
Log.error('Workflow file is invalid. Issues:');
|
||||
for (const validationError of validationErrors) {
|
||||
for (const formError of validationError.metadata.formErrors) {
|
||||
Log.error(`- ${formError}`);
|
||||
}
|
||||
|
||||
for (const [field, fieldErrors] of Object.entries(validationError.metadata.fieldErrors)) {
|
||||
Log.error(`- ${field}: ${fieldErrors.join(', ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const githubNotFoundError = error.graphQLErrors.find(
|
||||
e => e.extensions.errorCode === 'GITHUB_NOT_FOUND_ERROR'
|
||||
);
|
||||
if (githubNotFoundError) {
|
||||
Log.error(`GitHub repository not found. It is currently required to run workflows.`);
|
||||
Log.error(
|
||||
`Please check that the repository exists and that you have access to it. ${link(
|
||||
getProjectGitHubSettingsUrl(accountName, projectName)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateYamlExtension(fileName: string): void {
|
||||
const fileExtension = path.extname(fileName).toLowerCase();
|
||||
if (fileExtension !== '.yml' && fileExtension !== '.yaml') {
|
||||
throw new Error('File must have a .yml or .yaml extension');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export default class GitClient extends Client {
|
||||
Log.warn('EAS requires you to use a git repository for your project.');
|
||||
|
||||
const cwd = process.cwd();
|
||||
const repoRoot = PackageManagerUtils.findWorkspaceRoot(cwd) ?? cwd;
|
||||
const repoRoot = PackageManagerUtils.resolveWorkspaceRoot(cwd) ?? cwd;
|
||||
const confirmInit = await confirmAsync({
|
||||
message: `Would you like us to run 'git init' in ${
|
||||
this.maybeCwdOverride ?? repoRoot
|
||||
|
||||
@@ -109,20 +109,21 @@ export async function uploadAsync(params: UploadParams): Promise<UploadResult> {
|
||||
return retry(error);
|
||||
}
|
||||
|
||||
const body = await response.json().catch(() => null);
|
||||
const errorMessage = body?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
|
||||
|
||||
if (
|
||||
response.status === 408 ||
|
||||
response.status === 409 ||
|
||||
response.status === 429 ||
|
||||
(response.status >= 500 && response.status <= 599)
|
||||
) {
|
||||
const message = `Upload of "${filePath}" failed: ${response.statusText}`;
|
||||
const text = await response.text().catch(() => null);
|
||||
return retry(new Error(text ? `${message}\n${text}` : message));
|
||||
return retry(new Error(errorMessage));
|
||||
} else if (response.status === 413) {
|
||||
const message = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
|
||||
throw new Error(message);
|
||||
} else if (!response.ok) {
|
||||
throw new Error(`Upload of "${filePath}" failed: ${response.statusText}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import fs from 'fs/promises';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { WorkflowValidate } from '../../commands/workflow/validate';
|
||||
|
||||
jest.mock('fs/promises');
|
||||
jest.mock('../../log');
|
||||
jest.mock('../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: () => ({
|
||||
succeed: jest.fn(),
|
||||
fail: jest.fn(),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('WorkflowValidate', () => {
|
||||
const mockValidYaml = `
|
||||
name: workflow
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
steps:
|
||||
- run: echo "Hello"
|
||||
`;
|
||||
|
||||
const mockInvalidYaml = `
|
||||
name: workflow
|
||||
on: : : : invalid
|
||||
`;
|
||||
|
||||
let command: WorkflowValidate;
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
jest.resetAllMocks();
|
||||
process.exit = jest.fn(() => {
|
||||
throw new Error();
|
||||
});
|
||||
});
|
||||
|
||||
describe('file validation', () => {
|
||||
describe('when file exists', () => {
|
||||
beforeEach(() => {
|
||||
(fs.access as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it('validates a correct YAML file', async () => {
|
||||
(fs.readFile as jest.Mock).mockResolvedValue(mockValidYaml);
|
||||
command = new WorkflowValidate(['./workflow.yml'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts both .yml and .yaml extensions', async () => {
|
||||
(fs.readFile as jest.Mock).mockResolvedValue(mockValidYaml);
|
||||
|
||||
command = new WorkflowValidate(['./workflow.yml'], {} as any);
|
||||
await expect(command.runAsync()).resolves.not.toThrow();
|
||||
|
||||
command = new WorkflowValidate(['./workflow.yaml'], {} as any);
|
||||
await expect(command.runAsync()).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('throws error for empty YAML file', async () => {
|
||||
(fs.readFile as jest.Mock).mockResolvedValue('');
|
||||
command = new WorkflowValidate(['./workflow.yml'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws error for invalid YAML syntax', async () => {
|
||||
(fs.readFile as jest.Mock).mockResolvedValue(mockInvalidYaml);
|
||||
command = new WorkflowValidate(['./workflow.yml'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws error for invalid file extension', async () => {
|
||||
command = new WorkflowValidate(['./workflow.txt'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when file does not exist', () => {
|
||||
beforeEach(() => {
|
||||
(fs.access as jest.Mock).mockRejectedValue(new Error('ENOENT'));
|
||||
});
|
||||
|
||||
it('throws appropriate error', async () => {
|
||||
command = new WorkflowValidate(['./nonexistent.yml'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('handles unexpected errors gracefully', async () => {
|
||||
(fs.access as jest.Mock).mockResolvedValue(undefined);
|
||||
(fs.readFile as jest.Mock).mockRejectedValue(new Error('Unexpected error'));
|
||||
|
||||
command = new WorkflowValidate(['./workflow.yml'], {} as any);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"target": "ES2021",
|
||||
"target": "ES2022",
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"typeRoots": ["../../ts-declarations", "../../node_modules/@types", "./node_modules/@types"]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "13.2.2",
|
||||
"version": "14.2.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "7.23.5",
|
||||
"@expo/eas-build-job": "1.0.149",
|
||||
"@expo/eas-build-job": "1.0.156",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"updatedAt": "2022-07-18T15:00:00.000Z"
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"cli": {
|
||||
"$ref": "#/definitions/Cli"
|
||||
},
|
||||
@@ -202,6 +205,11 @@
|
||||
"description": "Custom workflow file name that will be used to run this build. You can also specify this property on platform level for platform-specific workflows. Learn more: https://docs.expo.dev/custom-builds/get-started/",
|
||||
"type": "string"
|
||||
},
|
||||
"environment": {
|
||||
"description": "Environment to use for the server-side defined EAS environment variables during build process and command execution. Learn more: https://docs.expo.dev/eas/environment-variables/",
|
||||
"markdownDescription": "Environment to use for the server-side defined EAS environment variables during build process and command execution. [Learn more](https://docs.expo.dev/eas/environment-variables/)",
|
||||
"enum": ["production", "preview", "development"]
|
||||
},
|
||||
"android": {
|
||||
"$ref": "#/definitions/BuildProfileAndroid"
|
||||
},
|
||||
|
||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user