Compare commits

..
Author SHA1 Message Date
Kadi Kraman 65130c666e Format urls as links 2024-10-10 13:26:24 +01:00
203 changed files with 2390 additions and 10681 deletions

No files matched your search

+12 -9
View File
@@ -1,20 +1,23 @@
commentConfiguration:
preamble: 'Subscribed to pull request'
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: ['wschurman', 'quinlanj']
mentions: ['EvanBacon', 'byCedric', 'wschurman']
- patterns: ['packages/eas-cli/src/commands/update/**']
mentions: ['wschurman', 'quinlanj']
- patterns: ['packages/eas-cli/src/channel/**']
mentions: ['wschurman', 'quinlanj']
- patterns: ['packages/eas-cli/src/commands/channel/**']
mentions: ['wschurman', 'quinlanj']
mentions: ['EvanBacon', 'byCedric', 'wschurman']
- 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']
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
contents: read
pull-requests: write
steps:
- uses: tobyhs/codemention@v1.4.0
- uses: tobyhs/codemention@v1.2.0
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
-174
View File
@@ -8,188 +8,14 @@ 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
### 🎉 New features
- Add EAS_SKIP_AUTO_FINGERPRINT to skip fingerprint computation on build ([#2675](https://github.com/expo/eas-cli/pull/2675) by [@quinlanj](https://github.com/quinlanj))
- Default build-logger-level to debug when EXPO_DEBUG is set. ([#2676](https://github.com/expo/eas-cli/pull/2676) by [@wschurman](https://github.com/wschurman))
## [13.1.1](https://github.com/expo/eas-cli/releases/tag/v13.1.1) - 2024-11-08
## [13.1.0](https://github.com/expo/eas-cli/releases/tag/v13.1.0) - 2024-11-07
### 🎉 New features
- Compute fingerprint for builds with SDK 52 and higher ([#2663](https://github.com/expo/eas-cli/pull/2663) by [@quinlanj](https://github.com/quinlanj))
## [13.0.1](https://github.com/expo/eas-cli/releases/tag/v13.0.1) - 2024-11-05
### 🧹 Chores
- Upgrade EAS Metadata with new properties from App Store. ([#2671](https://github.com/expo/eas-cli/pull/2671) by [@byCedric](https://github.com/byCedric))
## [13.0.0](https://github.com/expo/eas-cli/releases/tag/v13.0.0) - 2024-11-05
### 🛠 Breaking changes
- Resolve versioned expo config using `npx expo config` command instead of using fixed `@expo/config` version shipped with EAS CLI, if available. ([#2529](https://github.com/expo/eas-cli/pull/2529) by [@szdziedzic](https://github.com/szdziedzic))
### 🧹 Chores
- Add `macos-sonoma-14.6-xcode-16.1` image and `sdk-52` image tag to `eas.schema.json`. ([#2666](https://github.com/expo/eas-cli/pull/2666) by [@szdziedzic](https://github.com/szdziedzic))
- Update `@expo` packages versions. ([#2530](https://github.com/expo/eas-cli/pull/2530) by [@szdziedzic](https://github.com/szdziedzic))
## [12.6.2](https://github.com/expo/eas-cli/releases/tag/v12.6.2) - 2024-10-29
### 🐛 Bug fixes
- Make email case insensitive during Apple authentication. ([#2659](https://github.com/expo/eas-cli/pull/2659) by [@byCedric](https://github.com/byCedric))
## [12.6.1](https://github.com/expo/eas-cli/releases/tag/v12.6.1) - 2024-10-25
### 🧹 Chores
- Create dynamic logged in context field and clean up erroneous SessionManager context field uses. ([#2648](https://github.com/expo/eas-cli/pull/2648) by [@wschurman](https://github.com/wschurman))
## [12.6.0](https://github.com/expo/eas-cli/releases/tag/v12.6.0) - 2024-10-21
### 🎉 New features
- Add `eas channel:pause` and `eas channel:resume` commands to pause/resume update delivery to builds using specific update channels and display paused status in channel details output. ([#2614](https://github.com/expo/eas-cli/pull/2614) by [@fiberjw](https://github.com/fiberjw))
- Add interactivity to eas update:edit command. ([#2638](https://github.com/expo/eas-cli/pull/2638) by [@wschurman](https://github.com/wschurman))
## [12.5.4](https://github.com/expo/eas-cli/releases/tag/v12.5.4) - 2024-10-19
## [12.5.3](https://github.com/expo/eas-cli/releases/tag/v12.5.3) - 2024-10-16
## [12.5.2](https://github.com/expo/eas-cli/releases/tag/v12.5.2) - 2024-10-11
### 🐛 Bug fixes
- Disallow republishing an update that is being rolled-out. ([#2602](https://github.com/expo/eas-cli/pull/2602) by [@wschurman](https://github.com/wschurman))
- Bump `@expo/apple-utils` to `2.0.2` resolving the Apple authentication error. ([#2641](https://github.com/expo/eas-cli/pull/2641) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
- Implement new `worker` deploy API flow. ([#2601](https://github.com/expo/eas-cli/pull/2601) by [@kitten](https://github.com/kitten)))
- Unhide rollout-percentage flag on update publish command. ([#2608](https://github.com/expo/eas-cli/pull/2608) by [@wschurman](https://github.com/wschurman))
- Let folks know about the new concurrency add-on ([#2611](https://github.com/expo/eas-cli/pull/2611) by [@brentvatne](https://github.com/brentvatne))
## [12.5.1](https://github.com/expo/eas-cli/releases/tag/v12.5.1) - 2024-09-27
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "14.2.0",
"version": "12.5.1",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+159 -431
View File
@@ -81,8 +81,6 @@ eas --help COMMAND
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
* [`eas channel:list`](#eas-channellist)
* [`eas channel:pause [NAME]`](#eas-channelpause-name)
* [`eas channel:resume [NAME]`](#eas-channelresume-name)
* [`eas channel:rollout [CHANNEL]`](#eas-channelrollout-channel)
* [`eas channel:view [NAME]`](#eas-channelview-name)
* [`eas config`](#eas-config)
@@ -94,14 +92,6 @@ 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)
@@ -115,11 +105,14 @@ 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)
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
* [`eas update:edit [GROUPID]`](#eas-updateedit-groupid)
* [`eas update:list`](#eas-updatelist)
* [`eas update:republish`](#eas-updaterepublish)
* [`eas update:roll-back-to-embedded`](#eas-updateroll-back-to-embedded)
@@ -131,9 +124,6 @@ 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`
@@ -153,7 +143,7 @@ ALIASES
$ eas login
```
_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)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -170,7 +160,7 @@ ALIASES
$ eas logout
```
_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)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -187,7 +177,7 @@ ALIASES
$ eas whoami
```
_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)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -201,7 +191,7 @@ DESCRIPTION
display or change analytics settings
```
_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)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -253,7 +243,7 @@ DESCRIPTION
create a branch
```
_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)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -274,7 +264,7 @@ DESCRIPTION
delete a branch
```
_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)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -294,7 +284,7 @@ DESCRIPTION
list all branches
```
_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)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -314,7 +304,7 @@ DESCRIPTION
rename a branch
```
_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)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -337,7 +327,7 @@ DESCRIPTION
view a branch
```
_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)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -374,7 +364,7 @@ DESCRIPTION
start a build
```
_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)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -393,7 +383,7 @@ DESCRIPTION
cancel a build
```
_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)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -410,7 +400,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -429,7 +419,7 @@ DESCRIPTION
delete a build
```
_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)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:inspect`
@@ -464,7 +454,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -514,7 +504,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -543,7 +533,7 @@ DESCRIPTION
re-sign a build archive
```
_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)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -569,7 +559,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -619,7 +609,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -638,7 +628,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -657,7 +647,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -674,7 +664,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -695,7 +685,7 @@ DESCRIPTION
create a channel
```
_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)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -717,7 +707,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -737,51 +727,7 @@ DESCRIPTION
list all channels
```
_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]`
pause a channel to stop it from sending updates
```
USAGE
$ eas channel:pause [NAME] [--branch <value>] [--json --non-interactive]
ARGUMENTS
NAME Name of the channel to edit
FLAGS
--branch=<value> Name of the branch to point to
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
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/v14.2.0/packages/eas-cli/src/commands/channel/pause.ts)_
## `eas channel:resume [NAME]`
resume a channel to start sending updates
```
USAGE
$ eas channel:resume [NAME] [--branch <value>] [--json --non-interactive]
ARGUMENTS
NAME Name of the channel to edit
FLAGS
--branch=<value> Name of the branch to point to
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
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/v14.2.0/packages/eas-cli/src/commands/channel/resume.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -814,7 +760,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -837,7 +783,7 @@ DESCRIPTION
view a channel
```
_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)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -858,7 +804,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/v14.2.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -875,7 +821,7 @@ DESCRIPTION
manage credentials
```
_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)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -893,7 +839,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas device:create`
@@ -907,7 +853,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -927,7 +873,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -948,7 +894,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -969,7 +915,7 @@ DESCRIPTION
rename a registered device
```
_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)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -983,7 +929,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -997,206 +943,7 @@ DESCRIPTION
display environment info
```
_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)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -1304,7 +1051,7 @@ DESCRIPTION
validate the local store configuration
```
_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)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1321,7 +1068,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1338,7 +1085,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1368,7 +1115,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/v14.2.0/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1382,7 +1129,7 @@ DESCRIPTION
information about the current project
```
_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)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1405,7 +1152,7 @@ ALIASES
$ eas init
```
_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)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1423,7 +1170,82 @@ ALIASES
$ eas onboarding
```
_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)_
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v12.5.1/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/v12.5.1/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/v12.5.1/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/v12.5.1/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/v12.5.1/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit`
@@ -1454,7 +1276,7 @@ ALIASES
$ eas build:submit
```
_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)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1463,40 +1285,32 @@ 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>] [--environment development|preview|production] [--json --non-interactive]
[--clear-cache] [--emit-metadata] [-p android|ios|all] [--auto] [--private-key-path <value>] [--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
--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
-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/
--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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1504,19 +1318,17 @@ configure the project to support EAS Update
```
USAGE
$ eas update:configure [-p android|ios|all] [--environment development|preview|production] [--non-interactive]
$ eas update:configure [-p android|ios|all] [--non-interactive]
FLAGS
-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.
-p, --platform=(android|ios|all) [default: all] Platform to configure
--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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1537,31 +1349,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/v14.2.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:edit [GROUPID]`
edit all the updates in an update group
```
USAGE
$ eas update:edit [GROUPID] [--rollout-percentage <value>] [--branch <value>] [--json --non-interactive]
ARGUMENTS
GROUPID The ID of an update group to edit.
FLAGS
--branch=<value> Branch for which to list updates to select from
--json Enable JSON output, non-JSON messages will be printed to stderr.
--non-interactive Run the command in non-interactive mode.
--rollout-percentage=<value> Rollout percentage to set for a rollout update. The specified number must be an integer
between 1 and 100.
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/v14.2.0/packages/eas-cli/src/commands/update/edit.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1583,7 +1371,7 @@ DESCRIPTION
view the recent updates
```
_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)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1615,7 +1403,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:roll-back-to-embedded`
@@ -1623,11 +1411,12 @@ roll back to the embedded update
```
USAGE
$ 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]
$ eas update:roll-back-to-embedded [--branch <value>] [--channel <value>] [--message <value>] [-p android|ios|all] [--auto]
[--private-key-path <value>] [--json --non-interactive]
FLAGS
-p, --platform=(android|ios|all) [default: all]
--auto Use the current git branch and commit message for the EAS branch and update message
--branch=<value> Branch to publish the 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.
@@ -1637,13 +1426,12 @@ 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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -1663,7 +1451,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/v14.2.0/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/v12.5.1/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -1683,7 +1471,7 @@ DESCRIPTION
update group details
```
_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)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1704,7 +1492,7 @@ DESCRIPTION
create a webhook
```
_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)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1724,7 +1512,7 @@ DESCRIPTION
delete a webhook
```
_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)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1742,7 +1530,7 @@ DESCRIPTION
list webhooks
```
_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)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1764,7 +1552,7 @@ DESCRIPTION
update a webhook
```
_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)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1781,7 +1569,7 @@ DESCRIPTION
view a webhook
```
_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)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v12.5.1/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
@@ -1797,64 +1585,4 @@ 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 -->
+542 -3774
View File
File diff suppressed because it is too large. Load diff
+19 -27
View File
@@ -1,34 +1,35 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "14.2.0",
"version": "12.5.1",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "2.1.5",
"@expo/apple-utils": "1.8.0",
"@expo/code-signing-certificates": "0.0.5",
"@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/config": "8.5.4",
"@expo/config-plugins": "7.8.4",
"@expo/config-types": "50.0.0",
"@expo/eas-build-job": "1.0.136",
"@expo/eas-json": "12.5.0",
"@expo/env": "^0.3.0",
"@expo/json-file": "8.2.37",
"@expo/logger": "1.0.117",
"@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/multipart-body-parser": "1.1.0",
"@expo/osascript": "2.0.33",
"@expo/package-manager": "1.1.2",
"@expo/pkcs12": "0.0.8",
"@expo/plist": "0.0.20",
"@expo/plugin-help": "5.1.23",
"@expo/plugin-warn-if-update-available": "2.5.1",
"@expo/prebuild-config": "8.0.17",
"@expo/prebuild-config": "6.7.3",
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.2",
"@expo/steps": "1.0.156",
"@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.136",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -41,7 +42,6 @@
"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",
@@ -65,7 +65,7 @@
"mime": "3.0.0",
"minimatch": "5.1.2",
"minizlib": "3.0.1",
"nanoid": "3.3.8",
"nanoid": "3.3.4",
"node-fetch": "2.6.7",
"node-forge": "1.3.1",
"nullthrows": "1.1.1",
@@ -87,7 +87,6 @@
"untildify": "4.0.0",
"uuid": "9.0.1",
"wrap-ansi": "7.0.0",
"yaml": "2.6.0",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -97,7 +96,6 @@
"@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",
@@ -179,22 +177,16 @@
"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:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\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 ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
},
"update": {
"node": {
+18 -53
View File
@@ -1,20 +1,6 @@
{
"$schema": "http://json-schema.org/draft-06/schema",
"type": "object",
"properties": {
"configVersion": {
"enum": [0],
"description": "The EAS metadata store configuration version"
},
"apple": {
"$ref": "#/definitions/AppleConfig"
}
},
"additionalProperties": false,
"required": [
"configVersion",
"apple"
],
"definitions": {
"apple": {
"AppleKidsAge": {
@@ -337,8 +323,7 @@
"gambling",
"unrestrictedWebAccess",
"kidsAgeBand",
"ageRatingOverride",
"koreaAgeRatingOverride"
"seventeenPlus"
],
"defaultSnippets": [
{
@@ -360,9 +345,7 @@
"gambling": false,
"unrestrictedWebAccess": false,
"kidsAgeBand": null,
"ageRatingOverride": "NONE",
"koreaAgeRatingOverride": "NONE",
"lootBox": false
"seventeenPlus": false
}
},
{
@@ -384,9 +367,7 @@
"unrestrictedWebAccess": false,
"gambling": false,
"kidsAgeBand": null,
"ageRatingOverride": "${13|NONE,SEVENTEEN_PLUS,UNRATED|}",
"koreaAgeRatingOverride": "${14|NONE,FIFTEEN_PLUS,NINETEEN_PLUS|}",
"lootBox": false
"seventeenPlus": false
}
}
],
@@ -461,24 +442,8 @@
},
"seventeenPlus": {
"type": "boolean",
"deprecated": true,
"deprecationMessage": "This property is deprecated, use `ageRatingOverride` instead.",
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
},
"ageRatingOverride": {
"$ref": "#/definitions/apple/AppleAgeRatingOverride",
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 17, you can manually set the age rating to 17+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
},
"koreaAgeRatingOverride": {
"$ref": "#/definitions/apple/AppleKoreaAgeRatingOverride",
"description": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 15 or 19, you can manually set the age rating to 15+ or 19+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n@see https://help.apple.com/app-store-connect/#/dev599d50efb",
"markdownDescription": "If your app rates 12+ or lower, and you believe its content may not be suitable for children under 15 or 19, you can manually set the age rating to 15+ or 19+.\n\nIf your app will be distributed through alternative app marketplaces or websites in the European Union and you believe your app is [Unrated](https://developer.apple.com/help/app-store-connect/reference/age-ratings), set this to `UNRATED`.\n\n[Learn more](https://help.apple.com/app-store-connect/#/dev599d50efb)"
},
"lootBox": {
"type": "boolean",
"description": "Does your app contain purchasable loot boxes? These virtual containers provide players with in-game items, including functional cards or cosmetic modifications, based on random chance."
}
}
},
@@ -600,20 +565,6 @@
"maxLength": 4000
}
}
},
"AppleAgeRatingOverride": {
"enum": [
"NONE",
"SEVENTEEN_PLUS",
"UNRATED"
]
},
"AppleKoreaAgeRatingOverride": {
"enum": [
"NONE",
"FIFTEEN_PLUS",
"NINETEEN_PLUS"
]
}
},
"AppleConfig": {
@@ -824,5 +775,19 @@
}
}
}
}
},
"properties": {
"configVersion": {
"enum": [0],
"description": "The EAS metadata store configuration version"
},
"apple": {
"$ref": "#/definitions/AppleConfig"
}
},
"additionalProperties": false,
"required": [
"configVersion",
"apple"
]
}
@@ -1,4 +1,4 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import { AppVersionSource, EasJson } from '@expo/eas-json';
import { Command, Config } from '@oclif/core';
import { vol } from 'memfs';
+8 -11
View File
@@ -1,5 +1,4 @@
import chalk from 'chalk';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { BranchNotFoundError } from './utils';
@@ -9,11 +8,10 @@ import { withErrorHandlingAsync } from '../graphql/client';
import {
CreateUpdateBranchForAppMutation,
CreateUpdateBranchForAppMutationVariables,
UpdateBranchBasicInfoFragment,
UpdateBranch,
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';
@@ -128,7 +126,7 @@ function renderPageOfBranches(
export async function createUpdateBranchOnAppAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, name }: CreateUpdateBranchForAppMutationVariables
): Promise<UpdateBranchBasicInfoFragment> {
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
const result = await withErrorHandlingAsync(
graphqlClient
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
@@ -137,17 +135,15 @@ export async function createUpdateBranchOnAppAsync(
updateBranch {
createUpdateBranchForApp(appId: $appId, name: $name) {
id
...UpdateBranchBasicInfoFragment
name
}
}
}
${print(UpdateBranchBasicInfoFragmentNode)}
`,
{
appId,
name,
},
{ additionalTypenames: ['UpdateBranch'] }
}
)
.toPromise()
);
@@ -167,21 +163,22 @@ export async function ensureBranchExistsAsync(
appId: string;
branchName: string;
}
): Promise<{ branch: UpdateBranchBasicInfoFragment; createdBranch: boolean }> {
): Promise<{ branchId: string; createdBranch: boolean }> {
try {
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
appId,
name: branchName,
});
return { branch: updateBranch, createdBranch: false };
const { id } = updateBranch;
return { branchId: id, createdBranch: false };
} catch (error) {
if (error instanceof BranchNotFoundError) {
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
appId,
name: branchName,
});
return { branch: newUpdateBranch, createdBranch: true };
return { branchId: newUpdateBranch.id, createdBranch: true };
} else {
throw error;
}
+4 -2
View File
@@ -1,7 +1,9 @@
import { Client } from '../vcs/vcs';
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
return await vcsClient.getBranchNameAsync();
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
return (
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
);
}
export class BranchNotFoundError extends Error {
@@ -115,10 +115,6 @@ export async function prepareJobAsync(
},
}),
loggerLevel: ctx.loggerLevel,
// Technically, these are unused, but let's include them here for type consistency.
// See: https://github.com/expo/eas-build/pull/454
appId: ctx.projectId,
initiatingUserId: ctx.user.id,
};
return sanitizeBuildJob(job) as Android.Job;
+6 -15
View File
@@ -20,11 +20,7 @@ import { getNextVersionCode } from '../../project/android/versions';
import { resolveWorkflowAsync } from '../../project/workflow';
import { Client } from '../../vcs/vcs';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import {
bumpAppVersionAsync,
ensureStaticConfigExists,
getVersionConfigTarget,
} from '../utils/version';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
export enum BumpStrategy {
APP_VERSION,
@@ -57,11 +53,9 @@ 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,
version: exp.version,
versionCode: exp.android?.versionCode,
});
Log.log('Synchronized versions with build gradle');
@@ -85,7 +79,7 @@ export async function bumpVersionInAppJsonAsync({
if (bumpStrategy === BumpStrategy.APP_VERSION) {
const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0';
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.ANDROID });
await bumpAppVersionAsync({ appVersion, projectDir, exp });
} else {
const versionCode = AndroidConfig.Version.getVersionCode(exp);
const bumpedVersionCode = getNextVersionCode(versionCode);
@@ -124,11 +118,9 @@ 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,
appVersion: exp.version,
};
}
}
@@ -210,7 +202,6 @@ 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) {
@@ -239,7 +230,7 @@ export async function resolveRemoteVersionCodeAsync(
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
buildVersion: currentBuildVersion,
runtimeVersion:
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
@@ -263,7 +254,7 @@ export async function resolveRemoteVersionCodeAsync(
appId: projectId,
platform: AppPlatform.Android,
applicationIdentifier: applicationId,
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
buildVersion: String(nextBuildVersion),
runtimeVersion:
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
+13 -71
View File
@@ -66,7 +66,6 @@ import { maybeUploadFingerprintAsync } from '../project/maybeUploadFingerprintAs
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
import { uploadFileAtPathToGCSAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { createFingerprintAsync } from '../utils/fingerprintCli';
import { printJsonOnlyOutput } from '../utils/json';
import { createProgressTracker } from '../utils/progress';
import { sleepAsync } from '../utils/promise';
@@ -175,9 +174,8 @@ export async function prepareBuildRequestForPlatformAsync<
}
assert(projectArchive);
const runtimeAndFingerprintMetadata =
await computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync(ctx);
const metadata = await collectMetadataAsync(ctx, runtimeAndFingerprintMetadata);
const runtimeMetadata = await createAndMaybeUploadFingerprintAsync(ctx);
const metadata = await collectMetadataAsync(ctx, runtimeMetadata);
const buildParams = resolveBuildParamsInput(ctx, metadata);
const job = await builder.prepareJobAsync(ctx, {
projectArchive,
@@ -488,8 +486,8 @@ async function handleSingleBuildProgressAsync(
spinner.succeed('Build finished');
return { refetch: false };
case BuildStatus.New:
spinner.text = `Build concurrency limit reached for your account. Build will enter queue once a concurrency becomes available. Add additional concurrencies at ${link(
formatAccountBillingUrl(accountName)
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${link(
formatAccountSubscriptionsUrl(accountName)
)}.`;
break;
case BuildStatus.InQueue: {
@@ -511,7 +509,7 @@ async function handleSingleBuildProgressAsync(
Log.log('Start builds sooner in the priority queue.');
Log.log(
`Sign up for EAS Production or Enterprise at ${link(
formatAccountBillingUrl(accountName)
formatAccountSubscriptionsUrl(accountName)
)}`
);
}
@@ -658,39 +656,18 @@ function formatEstimatedWaitTime(estimatedWaitTimeLeftSeconds: number): string {
}
}
function formatAccountBillingUrl(accountName: string): string {
return new URL(`/accounts/${accountName}/settings/billing`, getExpoWebsiteBaseUrl()).toString();
function formatAccountSubscriptionsUrl(accountName: string): string {
return new URL(
`/accounts/${accountName}/settings/subscriptions`,
getExpoWebsiteBaseUrl()
).toString();
}
async function computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<{
runtimeVersion?: string | undefined;
fingerprintHash?: string | undefined;
fingerprintSource?: FingerprintSource | undefined;
}> {
const runtimeAndFingerprintMetadata =
await computeAndMaybeUploadFingerprintFromExpoUpdatesAsync(ctx);
if (!runtimeAndFingerprintMetadata?.fingerprintHash) {
const fingerprint = await computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync(ctx);
return {
...runtimeAndFingerprintMetadata,
...fingerprint,
};
} else {
return {
...runtimeAndFingerprintMetadata,
fingerprintHash: runtimeAndFingerprintMetadata.runtimeVersion,
};
}
}
async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Platform>(
async function createAndMaybeUploadFingerprintAsync<T extends Platform>(
ctx: BuildContext<T>
): Promise<{
runtimeVersion?: string;
fingerprintSource?: FingerprintSource;
fingerprintHash?: string;
}> {
const resolvedRuntimeVersion = await resolveRuntimeVersionAsync({
exp: ctx.exp,
@@ -715,45 +692,10 @@ async function computeAndMaybeUploadFingerprintFromExpoUpdatesAsync<T extends Pl
};
}
const uploadedFingerprint = await maybeUploadFingerprintAsync({
hash: resolvedRuntimeVersion.runtimeVersion,
return await maybeUploadFingerprintAsync({
runtimeVersion: resolvedRuntimeVersion.runtimeVersion,
fingerprint: resolvedRuntimeVersion.fingerprint,
graphqlClient: ctx.graphqlClient,
localBuildMode: ctx.localBuildOptions.localBuildMode,
});
return {
runtimeVersion: uploadedFingerprint.hash,
fingerprintSource: uploadedFingerprint.fingerprintSource,
fingerprintHash: resolvedRuntimeVersion.fingerprintHash ?? undefined,
};
}
async function computeAndMaybeUploadFingerprintWithoutExpoUpdatesAsync<T extends Platform>(
ctx: BuildContext<T>,
{ debug }: { debug?: boolean } = {}
): Promise<{
fingerprintHash?: string;
fingerprintSource?: FingerprintSource;
}> {
const fingerprint = await createFingerprintAsync(ctx.projectDir, {
workflow: ctx.workflow,
platforms: [ctx.platform],
env: ctx.env,
});
if (!fingerprint) {
return {};
}
const uploadedFingerprint = await maybeUploadFingerprintAsync({
hash: fingerprint.hash,
fingerprint: {
fingerprintSources: fingerprint.sources,
isDebugFingerprintSource: debug ?? false,
},
graphqlClient: ctx.graphqlClient,
localBuildMode: ctx.localBuildOptions.localBuildMode,
});
return {
fingerprintHash: uploadedFingerprint.hash,
fingerprintSource: uploadedFingerprint.fingerprintSource,
};
}
@@ -54,10 +54,9 @@ async function resolveEnvVarsAsync({
projectId: string;
}): Promise<Env> {
const environment =
buildProfile.environment?.toUpperCase() ??
resolveSuggestedEnvironmentForBuildProfileConfiguration(buildProfile);
buildProfile.environment?.toUpperCase() ?? process.env.EAS_CURRENT_ENVIRONMENT;
if (!isEnvironment(environment)) {
if (!environment || !isEnvironment(environment)) {
Log.log(
`Loaded "env" configuration for the "${buildProfileName}" profile: ${
buildProfile.env && Object.keys(buildProfile.env).length > 0
@@ -82,44 +81,21 @@ async function resolveEnvVarsAsync({
.map(({ name, value }) => [name, value])
) as Record<string, string>;
if (Object.keys(serverEnvVars).length > 0) {
Log.log(
`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 with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
);
}
if (buildProfile.env && Object.keys(buildProfile.env).length > 0) {
Log.log(
`Environment variables loaded from the "${buildProfileName}" build profile "env" configuration: ${
buildProfile.env && Object.keys(buildProfile.env).join(', ')
}.`
);
}
if (
buildProfile.env &&
Object.keys(buildProfile.env).length > 0 &&
Object.keys(serverEnvVars).length > 0
) {
const overlappingKeys = Object.keys(serverEnvVars).filter(
key => buildProfile.env && Object.keys(buildProfile.env).includes(key)
);
if (overlappingKeys.length > 0) {
Log.warn(
`The following environment variables are defined in both the "${buildProfileName}" build profile "env" configuration and the "${environment.toLowerCase()}" environment on EAS servers: ${overlappingKeys.join(
', '
)}. The values from the build profile configuration will be used.`
);
}
}
Log.newLine();
const envVarsWithSource: Record<string, 'build profile' | 'EAS server'> = {
...Object.fromEntries(Object.keys(serverEnvVars).map(key => [key, 'EAS server'])),
...(buildProfile.env
? Object.fromEntries(Object.keys(buildProfile.env).map(key => [key, 'build profile']))
: null),
};
Log.log(
`Loaded "env" configuration for the "${buildProfileName}" profile and "${environment.toLowerCase()}" environment: ${
Object.keys(envVarsWithSource).length > 0
? Object.keys(envVarsWithSource)
.map(key => `${key} (source: ${envVarsWithSource[key]})`)
.join('\n')
: 'no environment variables specified'
}\n${learnMore('https://docs.expo.dev/build-reference/variables/')}`
);
return { ...serverEnvVars, ...buildProfile.env };
} catch (e) {
@@ -131,27 +107,3 @@ 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,6 +5,7 @@ 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 {
@@ -300,6 +301,27 @@ 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', () => {
@@ -110,10 +110,6 @@ export async function prepareJobAsync(
},
}),
loggerLevel: ctx.loggerLevel,
// Technically, these are unused, but let's include them here for type consistency.
// See: https://github.com/expo/eas-build/pull/454
appId: ctx.projectId,
initiatingUserId: ctx.user.id,
};
return sanitizeBuildJob(job) as Ios.Job;
}
+9 -17
View File
@@ -21,11 +21,7 @@ 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,
getVersionConfigTarget,
} from '../utils/version';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
@@ -52,11 +48,9 @@ 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,
version: exp.version,
buildNumber: exp.ios?.buildNumber,
targets,
});
@@ -79,7 +73,7 @@ export async function bumpVersionInAppJsonAsync({
Log.addNewLineIfNone();
if (bumpStrategy === BumpStrategy.APP_VERSION) {
const appVersion = IOSConfig.Version.getVersion(exp);
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
await bumpAppVersionAsync({ appVersion, projectDir, exp });
} else {
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
if (isValidBuildNumber(buildNumber)) {
@@ -115,14 +109,14 @@ function validateShortVersion({
}): void {
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
if (workflow === Workflow.MANAGED) {
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(
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(
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
)}`
);
} else {
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(
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(
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
)}`
);
@@ -147,10 +141,8 @@ export async function readShortVersionAsync(
validateShortVersion({ shortVersion, workflow });
return shortVersion;
} else {
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
const shortVersion = versionGetter(exp);
validateShortVersion({ shortVersion, workflow });
return shortVersion;
validateShortVersion({ shortVersion: exp.version, workflow });
return exp.version;
}
}
+1 -1
View File
@@ -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.152';
const PLUGIN_PACKAGE_VERSION = '1.0.135';
export enum LocalBuildMode {
/**
+3 -5
View File
@@ -16,9 +16,8 @@ import { easCliVersion } from '../utils/easCli';
export async function collectMetadataAsync<T extends Platform>(
ctx: BuildContext<T>,
runtimeAndFingerprintMetadata: {
runtimeMetadata: {
runtimeVersion?: string | undefined;
fingerprintHash?: string | undefined;
fingerprintSource?: FingerprintSource | undefined;
}
): Promise<Metadata> {
@@ -31,9 +30,8 @@ export async function collectMetadataAsync<T extends Platform>(
workflow: ctx.workflow,
credentialsSource: ctx.buildProfile.credentialsSource,
sdkVersion: ctx.exp.sdkVersion,
runtimeVersion: runtimeAndFingerprintMetadata?.runtimeVersion,
fingerprintHash: runtimeAndFingerprintMetadata?.fingerprintHash,
fingerprintSource: runtimeAndFingerprintMetadata?.fingerprintSource,
runtimeVersion: runtimeMetadata?.runtimeVersion,
fingerprintSource: runtimeMetadata?.fingerprintSource,
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
...channelObject,
distribution,
@@ -1,4 +1,4 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import { Env, Platform, Workflow } from '@expo/eas-build-job';
import {
AppVersionSource,
@@ -381,7 +381,7 @@ async function prepareAndStartBuildAsync({
vcsClient,
getDynamicPrivateProjectConfigAsync,
customBuildConfigMetadata,
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
buildLoggerLevel: flags.buildLoggerLevel,
freezeCredentials: flags.freezeCredentials,
repack: flags.repack,
env,
@@ -1,148 +0,0 @@
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');
});
});
-22
View File
@@ -50,25 +50,3 @@ export function getUpdateGroupUrl(
getExpoWebsiteBaseUrl()
).toString();
}
export function getWorkflowRunUrl(
accountName: string,
projectName: string,
workflowRunId: string
): string {
return new URL(
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
projectName
)}/workflows/${workflowRunId}`,
getExpoWebsiteBaseUrl()
).toString();
}
export function getProjectGitHubSettingsUrl(accountName: string, projectName: string): string {
return new URL(
`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(
projectName
)}/github`,
getExpoWebsiteBaseUrl()
).toString();
}
+3 -58
View File
@@ -1,5 +1,4 @@
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';
@@ -19,25 +18,21 @@ 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(fieldName)} from ${chalk.bold(appVersion)} to ${chalk.bold(
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold(
bumpedAppVersion
)}`
);
} else {
Log.log(`${chalk.bold(fieldName)} = ${chalk.bold(appVersion)} is not a valid semver`);
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`);
bumpedAppVersion = (
await promptAsync({
type: 'text',
@@ -47,56 +42,6 @@ export async function bumpAppVersionAsync({
).bumpedAppVersion;
}
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
versionUpdater(config, bumpedAppVersion);
config.version = 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;
},
};
}
+6 -10
View File
@@ -27,28 +27,24 @@ 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)
))) &&
!googleServicesEnvVar
(await ctx.vcsClient.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
) {
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 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'
`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'
)}`
);
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();
}
}
@@ -128,7 +128,6 @@ export const testChannelObject: UpdateChannelObject = {
branchMapping:
'{"data":[{"branchId":"754bf17f-efc0-46ab-8a59-a03f20e53e9b","branchMappingLogic":{"operand":0.15,"clientKey":"rolloutToken","branchMappingOperator":"hash_lt"}},{"branchId":"6941a8dd-5c0a-48bc-8876-f49c88ed419f","branchMappingLogic":"true"}],"version":0}',
updateBranches: [testUpdateBranch1, testUpdateBranch2],
isPaused: false,
__typename: 'UpdateChannel',
};
+26 -34
View File
@@ -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, { learnMore } from '../log';
import Log from '../log';
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
import formatFields from '../utils/formatFields';
import { printJsonOnlyOutput } from '../utils/json';
@@ -110,11 +110,7 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
}
): Promise<void> {
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, { appId, channelName });
renderChannelHeaderContent({
channelName: channel.name,
channelId: channel.id,
isPaused: channel.isPaused,
});
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
if (paginatedQueryOptions.nonInteractive) {
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
@@ -171,11 +167,7 @@ function renderPageOfChannels(
printJsonOnlyOutput({ currentPage });
} else {
for (const channel of currentPage) {
renderChannelHeaderContent({
channelName: channel.name,
channelId: channel.id,
isPaused: channel.isPaused,
});
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
Log.addNewLineIfNone();
logChannelDetails(channel);
@@ -204,11 +196,9 @@ function renderPageOfBranchesOnChannel(
function renderChannelHeaderContent({
channelName,
channelId,
isPaused,
}: {
channelName: string;
channelId: string;
isPaused: boolean;
}): void {
Log.addNewLineIfNone();
Log.log(chalk.bold('Channel:'));
@@ -216,7 +206,6 @@ function renderChannelHeaderContent({
formatFields([
{ label: 'Name', value: channelName },
{ label: 'ID', value: channelId },
{ label: 'Status', value: isPaused ? 'Paused' : 'Active' },
])
);
Log.addNewLineIfNone();
@@ -303,6 +292,7 @@ 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
@@ -318,7 +308,7 @@ export async function createAndLinkChannelAsync(
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
): Promise<ChannelBasicInfo> {
let branchId: string;
let hasCreatedBranch = false;
let branchMessage: string;
try {
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
@@ -326,6 +316,7 @@ 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, {
@@ -333,7 +324,7 @@ export async function createAndLinkChannelAsync(
name: channelName,
});
branchId = newBranch.id;
hasCreatedBranch = true;
branchMessage = `We also went ahead and made a branch with the same name`;
} else {
throw error;
}
@@ -356,25 +347,26 @@ 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();
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}`
);
}
Log.withTick(
`Created a new channel on project ${chalk.bold(
await getDisplayNameForProjectIdAsync(graphqlClient, appId)
)}`
);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: newChannel.id },
])
);
Log.addNewLineIfNone();
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
Log.log(
formatFields([
{ label: 'Name', value: newChannel.name },
{ label: 'ID', value: branchId },
])
);
}
return newChannel;
}
@@ -6,7 +6,6 @@ import nullthrows from 'nullthrows';
import AnalyticsContextField from './context/AnalyticsContextField';
import ContextField from './context/ContextField';
import DynamicLoggedInContextField from './context/DynamicLoggedInContextField';
import {
DynamicPrivateProjectConfigContextField,
DynamicPublicProjectConfigContextField,
@@ -16,8 +15,6 @@ import MaybeLoggedInContextField from './context/MaybeLoggedInContextField';
import { OptionalPrivateProjectConfigContextField } from './context/OptionalPrivateProjectConfigContextField';
import { PrivateProjectConfigContextField } from './context/PrivateProjectConfigContextField';
import ProjectDirContextField from './context/ProjectDirContextField';
import { ProjectIdContextField } from './context/ProjectIdContextField';
import { ServerSideEnvironmentVariablesContextField } from './context/ServerSideEnvironmentVariablesContextField';
import SessionManagementContextField from './context/SessionManagementContextField';
import VcsClientContextField from './context/VcsClientContextField';
import { EasCommandError } from './errors';
@@ -26,7 +23,6 @@ import {
CommandEvent,
createAnalyticsAsync,
} from '../analytics/AnalyticsManager';
import { EnvironmentVariableEnvironment } from '../graphql/generated';
import Log from '../log';
import SessionManager from '../user/SessionManager';
import { Client } from '../vcs/vcs';
@@ -47,26 +43,8 @@ export type ContextOutput<
[P in keyof T]: T[P];
};
type GetContextType<Type> = {
[Property in keyof Type]: any;
};
const BASE_GRAPHQL_ERROR_MESSAGE: string = 'GraphQL request failed.';
interface BaseGetContextAsyncArgs {
nonInteractive: boolean;
vcsClientOverride?: Client;
}
interface GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument
extends BaseGetContextAsyncArgs {
withServerSideEnvironment: EnvironmentVariableEnvironment | null;
}
interface GetContextAsyncArgsWithoutServerSideEnvironmentArgument extends BaseGetContextAsyncArgs {
withServerSideEnvironment?: never;
}
export default abstract class EasCommand extends Command {
protected static readonly ContextOptions = {
/**
@@ -83,16 +61,8 @@ export default abstract class EasCommand extends Command {
MaybeLoggedIn: {
maybeLoggedIn: new MaybeLoggedInContextField(),
},
/**
* Specify this context if the logged-in requirement is only necessary in a particular execution of the command.
*/
DynamicLoggedIn: {
// eslint-disable-next-line async-protect/async-suffix
getDynamicLoggedInAsync: new DynamicLoggedInContextField(),
},
/**
* Specify this context requirement if the command needs to mutate the user session.
* @deprecated Should not be used outside of session management commands, which currently only includes `login` and `logout`.
*/
SessionManagment: {
sessionManager: new SessionManagementContextField(),
@@ -102,7 +72,7 @@ export default abstract class EasCommand extends Command {
* run within a project directory, null otherwise.
*/
OptionalProjectConfig: {
optionalPrivateProjectConfig: new OptionalPrivateProjectConfigContextField(),
privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
},
/**
* Require this command to be run in a project directory. Return the project directory in the context.
@@ -137,16 +107,6 @@ export default abstract class EasCommand extends Command {
Vcs: {
vcsClient: new VcsClientContextField(),
},
ServerSideEnvironmentVariables: {
// eslint-disable-next-line async-protect/async-suffix
getServerSideEnvironmentVariablesAsync: new ServerSideEnvironmentVariablesContextField(),
},
/**
* Require the project to be identified and registered on server. Returns the project ID evaluated from the app config.
*/
ProjectId: {
projectId: new ProjectIdContextField(),
},
};
/**
@@ -181,19 +141,7 @@ export default abstract class EasCommand extends Command {
} = object,
>(
commandClass: { contextDefinition: ContextInput<C> },
{
nonInteractive,
vcsClientOverride,
// if specified and not null, the env vars from the selected environment will be fetched from the server
// to resolve dynamic config (if dynamic config context is used) and enable getServerSideEnvironmentVariablesAsync function (if server side environment variables context is used)
withServerSideEnvironment,
}: C extends
| GetContextType<typeof EasCommand.ContextOptions.ProjectConfig>
| GetContextType<typeof EasCommand.ContextOptions.DynamicProjectConfig>
| GetContextType<typeof EasCommand.ContextOptions.OptionalProjectConfig>
| GetContextType<typeof EasCommand.ContextOptions.ServerSideEnvironmentVariables>
? GetContextAsyncArgsWithRequiredServerSideEnvironmentArgument
: GetContextAsyncArgsWithoutServerSideEnvironmentArgument
{ nonInteractive, vcsClientOverride }: { nonInteractive: boolean; vcsClientOverride?: Client }
): Promise<ContextOutput<C>> {
const contextDefinition = commandClass.contextDefinition;
@@ -207,7 +155,6 @@ export default abstract class EasCommand extends Command {
sessionManager: this.sessionManager,
analytics: this.analytics,
vcsClientOverride,
withServerSideEnvironment,
}),
]);
}
+1 -9
View File
@@ -38,12 +38,7 @@ export async function fetchBuildsAsync({
}: {
graphqlClient: ExpoGraphqlClient;
projectId: string;
filters?: {
statuses?: BuildStatus[];
platform?: RequestedPlatform;
profile?: string;
hasFingerprint?: boolean;
};
filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string };
}): Promise<BuildFragment[]> {
let builds: BuildFragment[];
const queryFilters: InputMaybe<BuildFilter> = {};
@@ -53,9 +48,6 @@ 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,5 +1,4 @@
import { Analytics } from '../../analytics/AnalyticsManager';
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
import SessionManager from '../../user/SessionManager';
import { Client } from '../../vcs/vcs';
@@ -8,10 +7,6 @@ export interface ContextOptions {
analytics: Analytics;
nonInteractive: boolean;
vcsClientOverride?: Client;
/**
* If specified, env variables from the selected environment will be fetched from the server and used to evaluate the dynamic config.
*/
withServerSideEnvironment?: EnvironmentVariableEnvironment | null;
}
export default abstract class ContextField<T> {
@@ -1,36 +0,0 @@
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
import { LoggedInAuthenticationInfo } from '../../user/SessionManager';
import { Actor } from '../../user/User';
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
import FeatureGating from '../gating/FeatureGating';
export type DynamicLoggedInContextFn = () => Promise<{
actor: Actor;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
authenticationInfo: LoggedInAuthenticationInfo;
}>;
export default class DynamicLoggedInContextField extends ContextField<DynamicLoggedInContextFn> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<DynamicLoggedInContextFn> {
return async () => {
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const featureGateServerValues: { [key: string]: boolean } = actor?.featureGates ?? {};
const graphqlClient = createGraphqlClient(authenticationInfo);
return {
actor,
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
graphqlClient,
authenticationInfo,
};
};
}
}
@@ -1,14 +1,12 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import ContextField, { ContextOptions } from './ContextField';
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
import {
ExpoConfigOptions,
getPrivateExpoConfigAsync,
getPublicExpoConfigAsync,
getPrivateExpoConfig,
getPublicExpoConfig,
} from '../../project/expoConfig';
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
@@ -21,34 +19,15 @@ export class DynamicPublicProjectConfigContextField extends ContextField<Dynamic
async getValueAsync({
nonInteractive,
sessionManager,
withServerSideEnvironment,
}: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => {
const expBefore = await getPublicExpoConfigAsync(projectDir, options);
const expBefore = getPublicExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
env: options?.env,
});
if (withServerSideEnvironment) {
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
environment: withServerSideEnvironment,
projectId,
graphqlClient,
});
options = {
...options,
env: {
...options?.env,
...serverSideEnvironmentVariables,
},
};
}
const exp = await getPublicExpoConfigAsync(projectDir, options);
const exp = getPublicExpoConfig(projectDir, options);
return {
exp,
projectDir,
@@ -62,34 +41,15 @@ export class DynamicPrivateProjectConfigContextField extends ContextField<Dynami
async getValueAsync({
nonInteractive,
sessionManager,
withServerSideEnvironment,
}: ContextOptions): Promise<DynamicConfigContextFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (options?: ExpoConfigOptions) => {
const expBefore = await getPrivateExpoConfigAsync(projectDir, options);
const expBefore = getPrivateExpoConfig(projectDir, options);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
env: options?.env,
});
if (withServerSideEnvironment) {
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
environment: withServerSideEnvironment,
projectId,
graphqlClient,
});
options = {
...options,
env: {
...options?.env,
...serverSideEnvironmentVariables,
},
};
}
const exp = await getPrivateExpoConfigAsync(projectDir, options);
const exp = getPrivateExpoConfig(projectDir, options);
return {
exp,
projectDir,
@@ -1,22 +1,19 @@
import ContextField, { ContextOptions } from './ContextField';
import { ExpoGraphqlClient, createGraphqlClient } from './contextUtils/createGraphqlClient';
import { LoggedInAuthenticationInfo } from '../../user/SessionManager';
import { Actor } from '../../user/User';
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
import FeatureGating from '../gating/FeatureGating';
type LoggedInContextType = {
export default class LoggedInContextField extends ContextField<{
actor: Actor;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
authenticationInfo: LoggedInAuthenticationInfo;
};
export default class LoggedInContextField extends ContextField<LoggedInContextType> {
async getValueAsync({
nonInteractive,
sessionManager,
}: ContextOptions): Promise<LoggedInContextType> {
}> {
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
actor: Actor;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
@@ -28,7 +25,6 @@ export default class LoggedInContextField extends ContextField<LoggedInContextTy
actor,
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
graphqlClient,
authenticationInfo,
};
}
}
@@ -4,18 +4,16 @@ import { Actor } from '../../user/User';
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
import FeatureGating from '../gating/FeatureGating';
type MaybeLoggedInContextType = {
export default class MaybeLoggedInContextField extends ContextField<{
actor: Actor | null;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
authenticationInfo: {
accessToken: string | null;
sessionSecret: string | null;
};
};
export default class MaybeLoggedInContextField extends ContextField<MaybeLoggedInContextType> {
async getValueAsync({ sessionManager }: ContextOptions): Promise<MaybeLoggedInContextType> {
}> {
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
actor: Actor | null;
featureGating: FeatureGating;
graphqlClient: ExpoGraphqlClient;
}> {
const authenticationInfo = {
accessToken: sessionManager.getAccessToken(),
sessionSecret: sessionManager.getSessionSecret(),
@@ -29,7 +27,6 @@ export default class MaybeLoggedInContextField extends ContextField<MaybeLoggedI
actor: actor ?? null,
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
graphqlClient,
authenticationInfo,
};
}
}
@@ -1,12 +1,10 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import { InvalidEasJsonError } from '@expo/eas-json/build/errors';
import ContextField, { ContextOptions } from './ContextField';
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
import { getPrivateExpoConfig } from '../../project/expoConfig';
export class OptionalPrivateProjectConfigContextField extends ContextField<
| {
@@ -16,11 +14,7 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
}
| undefined
> {
async getValueAsync({
nonInteractive,
sessionManager,
withServerSideEnvironment,
}: ContextOptions): Promise<
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
| {
projectId: string;
exp: ExpoConfig;
@@ -41,24 +35,11 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
return undefined;
}
const expBefore = await getPrivateExpoConfigAsync(projectDir);
const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
let serverSideEnvVars: Record<string, string> | undefined;
if (withServerSideEnvironment) {
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
environment: withServerSideEnvironment,
projectId,
graphqlClient,
});
serverSideEnvVars = serverSideEnvironmentVariables;
}
const exp = await getPrivateExpoConfigAsync(projectDir, { env: serverSideEnvVars });
const exp = getPrivateExpoConfig(projectDir);
return {
exp,
projectDir,
@@ -1,45 +1,26 @@
import { ExpoConfig } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import ContextField, { ContextOptions } from './ContextField';
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
import { getPrivateExpoConfig } from '../../project/expoConfig';
export class PrivateProjectConfigContextField extends ContextField<{
projectId: string;
exp: ExpoConfig;
projectDir: string;
}> {
async getValueAsync({
nonInteractive,
sessionManager,
withServerSideEnvironment,
}: ContextOptions): Promise<{
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
projectId: string;
exp: ExpoConfig;
projectDir: string;
}> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = await getPrivateExpoConfigAsync(projectDir);
const expBefore = getPrivateExpoConfig(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
let serverSideEnvVars: Record<string, string> | undefined;
if (withServerSideEnvironment) {
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
environment: withServerSideEnvironment,
projectId,
graphqlClient,
});
serverSideEnvVars = serverSideEnvironmentVariables;
}
const exp = await getPrivateExpoConfigAsync(projectDir, { env: serverSideEnvVars });
const exp = getPrivateExpoConfig(projectDir);
return {
projectId,
@@ -1,15 +0,0 @@
import ContextField, { ContextOptions } from './ContextField';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
export class ProjectIdContextField extends ContextField<string> {
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<string> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
const expBefore = await getPrivateExpoConfigAsync(projectDir);
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
nonInteractive,
});
return projectId;
}
}
@@ -1,42 +0,0 @@
import ContextField, { ContextOptions } from './ContextField';
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
import { getPublicExpoConfigAsync } from '../../project/expoConfig';
type GetServerSideEnvironmentVariablesFn = (
maybeEnv?: Record<string, string>
) => Promise<Record<string, string>>;
export class ServerSideEnvironmentVariablesContextField extends ContextField<GetServerSideEnvironmentVariablesFn> {
async getValueAsync({
nonInteractive,
sessionManager,
withServerSideEnvironment,
}: ContextOptions): Promise<GetServerSideEnvironmentVariablesFn> {
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
return async (maybeEnv?: Record<string, string>) => {
if (!withServerSideEnvironment) {
throw new Error(
'withServerSideEnvironment parameter is required to evaluate ServerSideEnvironmentVariablesContextField'
);
}
const exp = await getPublicExpoConfigAsync(projectDir, { env: maybeEnv });
const projectId = await getProjectIdAsync(sessionManager, exp, {
nonInteractive,
env: maybeEnv,
});
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const serverSideEnvironmentVariables = await loadServerSideEnvironmentVariablesAsync({
environment: withServerSideEnvironment,
projectId,
graphqlClient,
});
return serverSideEnvironmentVariables;
};
}
}
@@ -6,7 +6,6 @@ import { Role } from '../../../../graphql/generated';
import { AppQuery } from '../../../../graphql/queries/AppQuery';
import { learnMore } from '../../../../log';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { isExpoInstalled } from '../../../../project/projectUtils';
import SessionManager from '../../../../user/SessionManager';
import { findProjectRootAsync } from '../findProjectDirAndVerifyProjectSetupAsync';
import { getProjectIdAsync } from '../getProjectIdAsync';
@@ -22,7 +21,6 @@ jest.mock('../../../../ora', () => ({
}),
}));
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
jest.mock('../../../../project/projectUtils');
describe(getProjectIdAsync, () => {
let sessionManager: SessionManager;
@@ -73,7 +71,6 @@ describe(getProjectIdAsync, () => {
);
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
jest.mocked(isExpoInstalled).mockReturnValue(true);
});
it('gets the project ID from app config if exists', async () => {
@@ -225,7 +222,7 @@ describe(getProjectIdAsync, () => {
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
jest.mocked(modifyConfigAsync).mockResolvedValue({
type: 'success',
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
});
jest
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
@@ -42,7 +42,7 @@ async function ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
}
const maybeRepoRoot = PackageManagerUtils.resolveWorkspaceRoot(projectDir) ?? projectDir;
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
printCliVersionWarning = true;
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
@@ -1,4 +1,5 @@
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
import { getProjectConfigDescription } from '@expo/config';
import { ExpoConfig } from '@expo/config-types';
import { Env } from '@expo/eas-build-job';
import chalk from 'chalk';
@@ -7,11 +8,9 @@ import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync
import { AppQuery } from '../../../graphql/queries/AppQuery';
import Log, { learnMore } from '../../../log';
import { ora } from '../../../ora';
import {
createOrModifyExpoConfigAsync,
getPrivateExpoConfigAsync,
} from '../../../project/expoConfig';
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
import { toAppPrivacy } from '../../../project/projectUtils';
import SessionManager from '../../../user/SessionManager';
import { Actor, getActorUsername } from '../../../user/User';
@@ -27,7 +26,7 @@ export async function saveProjectIdToAppConfigAsync(
options: { env?: Env } = {}
): Promise<void> {
// NOTE(cedric): we disable plugins to avoid writing plugin-generated content to `expo.extra`
const exp = await getPrivateExpoConfigAsync(projectDir, { skipPlugins: true, ...options });
const exp = getPrivateExpoConfig(projectDir, { skipPlugins: true, ...options });
const result = await createOrModifyExpoConfigAsync(
projectDir,
{
@@ -196,6 +195,7 @@ export async function validateOrSetProjectIdAsync({
{
accountName: getAccountNameForEASProjectSync(exp, actor),
projectName: exp.slug,
privacy: toAppPrivacy(exp.privacy),
},
{
nonInteractive: options.nonInteractive,
@@ -1,59 +0,0 @@
import { ExpoGraphqlClient } from './createGraphqlClient';
import { EnvironmentVariableEnvironment } from '../../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
const cachedServerSideEnvironmentVariables: Record<
EnvironmentVariableEnvironment,
Record<string, string> | null
> = {
[EnvironmentVariableEnvironment.Development]: null,
[EnvironmentVariableEnvironment.Preview]: null,
[EnvironmentVariableEnvironment.Production]: null,
};
export async function loadServerSideEnvironmentVariablesAsync({
environment,
projectId,
graphqlClient,
}: {
environment: EnvironmentVariableEnvironment;
projectId: string;
graphqlClient: ExpoGraphqlClient;
}): Promise<Record<string, string>> {
// don't load environment variables if they were already loaded while executing a command
const cachedEnvVarsForEnvironment = cachedServerSideEnvironmentVariables[environment];
if (cachedEnvVarsForEnvironment) {
return cachedEnvVarsForEnvironment;
}
const environmentVariables = await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(
graphqlClient,
{
appId: projectId,
environment,
}
);
const serverEnvVars = Object.fromEntries(
environmentVariables
.filter(({ name, value }) => name && value)
.map(({ name, value }) => [name, value])
) as Record<string, string>;
if (Object.keys(serverEnvVars).length > 0) {
Log.log(
`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 with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
);
}
Log.newLine();
cachedServerSideEnvironmentVariables[environment] = serverEnvVars;
return serverEnvVars;
}
+17 -20
View File
@@ -1,6 +1,10 @@
import { Flags } from '@oclif/core';
import { EnvironmentVariableEnvironment } from '../graphql/generated';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableScope,
EnvironmentVariableVisibility,
} from '../graphql/generated';
// NOTE: not exactly true, but, provided mapToLowercase and upperCaseAsync
// are used in tandem, it saves on unnecessary typying in commands
@@ -52,19 +56,23 @@ export const EASVariableFormatFlag = {
};
export const EASVariableVisibilityFlag = {
visibility: Flags.enum<'plaintext' | 'sensitive' | 'secret'>({
visibility: Flags.enum<EnvironmentVariableVisibility>({
description: 'Visibility of the variable',
options: ['plaintext', 'sensitive', 'secret'],
options: mapToLowercase([
EnvironmentVariableVisibility.Secret,
EnvironmentVariableVisibility.Sensitive,
EnvironmentVariableVisibility.Public,
]),
parse: upperCaseAsync,
}),
};
export type EASEnvironmentVariableScopeFlagValue = 'project' | 'account';
export const EASEnvironmentVariableScopeFlag = {
scope: Flags.enum<EASEnvironmentVariableScopeFlagValue>({
export const EASVariableScopeFlag = {
scope: Flags.enum<EnvironmentVariableScope>({
description: 'Scope for the variable',
options: ['project', 'account'],
default: 'project',
options: mapToLowercase([EnvironmentVariableScope.Shared, EnvironmentVariableScope.Project]),
parse: upperCaseAsync,
default: EnvironmentVariableScope.Project,
}),
};
@@ -79,14 +87,3 @@ export const EasJsonOnlyFlag = {
description: 'Enable JSON output, non-JSON messages will be printed to stderr.',
}),
};
export const EasUpdateEnvironmentFlag = {
environment: Flags.enum<EnvironmentVariableEnvironment | null>({
description:
'Environment to use for the server-side defined EAS environment variables during command execution.',
options: mapToLowercase(Object.values(EnvironmentVariableEnvironment)),
parse: upperCaseAsync,
required: false,
default: null,
}),
};
@@ -12,14 +12,16 @@ export default class AccountView extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.MaybeLoggedIn,
...this.ContextOptions.SessionManagment,
};
async runAsync(): Promise<void> {
const {
maybeLoggedIn: { actor, authenticationInfo },
maybeLoggedIn: { actor },
sessionManager,
} = await this.getContextAsync(AccountView, { nonInteractive: true });
if (actor) {
const loggedInAs = authenticationInfo.accessToken
const loggedInAs = sessionManager.getAccessToken()
? `${getActorDisplayName(actor)} (authenticated using EXPO_TOKEN)`
: getActorDisplayName(actor);
Log.log(chalk.green(loggedInAs));
@@ -25,7 +25,7 @@ export default class BranchCreate extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
@@ -36,7 +36,7 @@ export default class BranchCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(BranchCreate);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
vcsClient,
} = await this.getContextAsync(BranchCreate, {
@@ -58,7 +58,7 @@ export default class BranchCreate extends EasCommand {
type: 'text',
name: 'name',
message: 'Provide a branch name:',
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
initial: await getDefaultBranchNameAsync(vcsClient),
validate: value => (value ? true : validationMessage),
}));
}
@@ -79,7 +79,7 @@ export default class BranchDelete extends EasCommand {
static override description = 'delete a branch';
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchDelete, { nonInteractive });
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
+2 -2
View File
@@ -13,14 +13,14 @@ export default class BranchList extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(BranchList);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchList, {
nonInteractive: flags['non-interactive'],
@@ -63,7 +63,7 @@ export default class BranchRename extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -72,7 +72,7 @@ export default class BranchRename extends EasCommand {
flags: { json: jsonFlag, from: currentName, to: newName, 'non-interactive': nonInteractive },
} = await this.parse(BranchRename);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchRename, {
nonInteractive,
+2 -2
View File
@@ -27,7 +27,7 @@ export default class BranchView extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
} = await this.parse(BranchView);
const { 'non-interactive': nonInteractive } = flags;
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BranchView, {
nonInteractive,
@@ -110,7 +110,7 @@ export default class BuildCancel extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
@@ -128,7 +128,7 @@ export default class BuildCancel extends EasCommand {
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildCancel, {
nonInteractive,
@@ -38,7 +38,6 @@ export default class BuildConfigure extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildConfigure, {
nonInteractive: false,
withServerSideEnvironment: null,
});
Log.log(
@@ -94,7 +94,7 @@ export default class BuildDelete extends EasCommand {
}),
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
@@ -112,7 +112,7 @@ export default class BuildDelete extends EasCommand {
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildDelete, { nonInteractive });
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
@@ -142,7 +142,6 @@ export default class Build extends EasCommand {
vcsClient,
} = await this.getContextAsync(Build, {
nonInteractive: flags.nonInteractive,
withServerSideEnvironment: null,
});
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
@@ -79,7 +79,6 @@ export default class BuildInspect extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildInspect, {
nonInteractive: false,
withServerSideEnvironment: null,
});
const outputDirectory = path.resolve(process.cwd(), flags.output);
@@ -7,7 +7,6 @@ import EasCommand from '../../commandUtils/EasCommand';
import { RequestedPlatform } from '../../platform';
import { enableJsonOutput } from '../../utils/json';
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
import NoVcsClient from '../../vcs/clients/noVcs';
/**
* This command will be run on the EAS Build workers, when building
@@ -64,8 +63,7 @@ export default class BuildInternal extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildInternal, {
nonInteractive: true,
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
withServerSideEnvironment: null,
vcsClientOverride: new GitNoCommitClient(),
});
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
+2 -2
View File
@@ -72,7 +72,7 @@ export default class BuildList extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
@@ -99,7 +99,7 @@ export default class BuildList extends EasCommand {
process.exit(1);
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, {
nonInteractive,
@@ -120,7 +120,6 @@ export default class BuildResign extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildResign, {
nonInteractive: flags.nonInteractive,
withServerSideEnvironment: null,
});
const maybeBuild = flags.maybeBuildId
+3 -2
View File
@@ -79,7 +79,8 @@ export default class Run extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.Vcs,
};
@@ -89,7 +90,7 @@ export default class Run extends EasCommand {
const queryOptions = getPaginatedQueryOptions(flags);
const {
loggedIn: { graphqlClient },
projectId,
privateProjectConfig: { projectId },
} = await this.getContextAsync(Run, {
nonInteractive: false,
});
@@ -54,7 +54,6 @@ export default class BuildVersionGetView extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildVersionGetView, {
nonInteractive: true,
withServerSideEnvironment: null,
});
if (!flags.platform && flags['non-interactive']) {
@@ -5,7 +5,6 @@ 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';
@@ -55,7 +54,6 @@ export default class BuildVersionSetView extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildVersionSetView, {
nonInteractive: false,
withServerSideEnvironment: null,
});
const platform = await selectPlatformAsync(flags.platform);
@@ -112,7 +110,6 @@ 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',
@@ -126,7 +123,7 @@ export default class BuildVersionSetView extends EasCommand {
appId: projectId,
platform: toAppPlatform(platform),
applicationIdentifier,
storeVersion: versionGetter(exp) ?? '1.0.0',
storeVersion: exp.version ?? '1.0.0',
buildVersion: String(version),
runtimeVersion:
(await getRuntimeVersionNullableAsync(projectDir, exp, platform)) ?? undefined,
@@ -73,7 +73,6 @@ export default class BuildVersionSyncView extends EasCommand {
vcsClient,
} = await this.getContextAsync(BuildVersionSyncView, {
nonInteractive: true,
withServerSideEnvironment: null,
});
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
+2 -2
View File
@@ -18,7 +18,7 @@ export default class BuildView extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
@@ -29,7 +29,7 @@ export default class BuildView extends EasCommand {
flags,
} = await this.parse(BuildView);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildView, {
nonInteractive: true,
@@ -23,7 +23,7 @@ export default class ChannelCreate extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -33,7 +33,7 @@ export default class ChannelCreate extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelCreate);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelCreate, {
nonInteractive,
@@ -31,7 +31,7 @@ export default class ChannelDelete extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -41,7 +41,7 @@ export default class ChannelDelete extends EasCommand {
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
} = await this.parse(ChannelDelete);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelDelete, {
nonInteractive,
@@ -74,7 +74,7 @@ export default class ChannelEdit extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -84,7 +84,7 @@ export default class ChannelEdit extends EasCommand {
flags: { branch: branchFlag, json, 'non-interactive': nonInteractive },
} = await this.parse(ChannelEdit);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelEdit, {
nonInteractive,
@@ -18,7 +18,7 @@ export default class ChannelList extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -27,7 +27,7 @@ export default class ChannelList extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelList, {
nonInteractive,
@@ -1,113 +0,0 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { selectChannelOnAppAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
PauseUpdateChannelMutation,
PauseUpdateChannelMutationVariables,
UpdateChannelBasicInfoFragment,
} from '../../graphql/generated';
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import { UpdateChannelBasicInfoFragmentNode } from '../../graphql/types/UpdateChannelBasicInfo';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export async function pauseUpdateChannelAsync(
graphqlClient: ExpoGraphqlClient,
{ channelId }: PauseUpdateChannelMutationVariables
): Promise<UpdateChannelBasicInfoFragment> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<PauseUpdateChannelMutation, PauseUpdateChannelMutationVariables>(
gql`
mutation PauseUpdateChannel($channelId: ID!) {
updateChannel {
pauseUpdateChannel(channelId: $channelId) {
id
...UpdateChannelBasicInfoFragment
}
}
}
${print(UpdateChannelBasicInfoFragmentNode)}
`,
{ channelId }
)
.toPromise()
);
const channel = data.updateChannel.pauseUpdateChannel;
if (!channel) {
throw new Error(`Could not find a channel with id: ${channelId}`);
}
return channel;
}
export default class ChannelPause extends EasCommand {
static override description = 'pause a channel to stop it from sending updates';
static override args = [
{
name: 'name',
required: false,
description: 'Name of the channel to edit',
},
];
static override flags = {
branch: Flags.string({
description: 'Name of the branch to point to',
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
args,
flags: { json, 'non-interactive': nonInteractive },
} = await this.parse(ChannelPause);
const {
projectId,
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelPause, {
nonInteractive,
});
if (json) {
enableJsonOutput();
}
const existingChannel = args.name
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId: projectId,
channelName: args.name,
})
: await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to edit',
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
});
const channel = await pauseUpdateChannelAsync(graphqlClient, {
channelId: existingChannel.id,
});
if (json) {
printJsonOnlyOutput(channel);
} else {
Log.withTick(chalk`Channel {bold ${channel.name}} is now paused.\n`);
Log.addNewLineIfNone();
Log.log(
chalk`Users with builds on channel {bold ${channel.name}} will no longer receive updates.`
);
}
}
}
@@ -1,111 +0,0 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { selectChannelOnAppAsync } from '../../channel/queries';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { withErrorHandlingAsync } from '../../graphql/client';
import {
ResumeUpdateChannelMutation,
ResumeUpdateChannelMutationVariables,
UpdateChannelBasicInfoFragment,
} from '../../graphql/generated';
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import { UpdateChannelBasicInfoFragmentNode } from '../../graphql/types/UpdateChannelBasicInfo';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
export async function resumeUpdateChannelAsync(
graphqlClient: ExpoGraphqlClient,
{ channelId }: ResumeUpdateChannelMutationVariables
): Promise<UpdateChannelBasicInfoFragment> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<ResumeUpdateChannelMutation, ResumeUpdateChannelMutationVariables>(
gql`
mutation ResumeUpdateChannel($channelId: ID!) {
updateChannel {
resumeUpdateChannel(channelId: $channelId) {
id
...UpdateChannelBasicInfoFragment
}
}
}
${print(UpdateChannelBasicInfoFragmentNode)}
`,
{ channelId }
)
.toPromise()
);
const channel = data.updateChannel.resumeUpdateChannel;
if (!channel) {
throw new Error(`Could not find a channel with id: ${channelId}`);
}
return channel;
}
export default class ChannelResume extends EasCommand {
static override description = 'resume a channel to start sending updates';
static override args = [
{
name: 'name',
required: false,
description: 'Name of the channel to edit',
},
];
static override flags = {
branch: Flags.string({
description: 'Name of the branch to point to',
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const {
args,
flags: { json, 'non-interactive': nonInteractive },
} = await this.parse(ChannelResume);
const {
projectId,
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelResume, {
nonInteractive,
});
if (json) {
enableJsonOutput();
}
const existingChannel = args.name
? await ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
appId: projectId,
channelName: args.name,
})
: await selectChannelOnAppAsync(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to edit',
paginatedQueryOptions: { json, nonInteractive, offset: 0 },
});
const channel = await resumeUpdateChannelAsync(graphqlClient, {
channelId: existingChannel.id,
});
if (json) {
printJsonOnlyOutput(channel);
} else {
Log.withTick(chalk`Channel {bold ${channel.name}} is now active.\n`);
Log.addNewLineIfNone();
Log.log(chalk`Users with builds on channel {bold ${channel.name}} will now receive updates.`);
}
}
}
@@ -138,7 +138,6 @@ export default class ChannelRollout extends EasCommand {
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelRollout, {
nonInteractive: argsAndFlags.nonInteractive,
withServerSideEnvironment: null,
});
if (argsAndFlags.json) {
enableJsonOutput();
@@ -26,7 +26,7 @@ export default class ChannelView extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -38,7 +38,7 @@ export default class ChannelView extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const { json: jsonFlag, 'non-interactive': nonInteractive } = flags;
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(ChannelView, {
nonInteractive,
+7 -4
View File
@@ -6,6 +6,7 @@ import chalk from 'chalk';
import { evaluateConfigWithEnvVarsAsync } from '../build/evaluateConfigWithEnvVarsAsync';
import EasCommand from '../commandUtils/EasCommand';
import { createGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../commandUtils/flags';
import { toAppPlatform } from '../graphql/types/AppPlatform';
import Log from '../log';
@@ -34,7 +35,7 @@ export default class Config extends EasCommand {
static override contextDefinition = {
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.DynamicLoggedIn,
...this.ContextOptions.SessionManagment,
};
async runAsync(): Promise<void> {
@@ -47,10 +48,9 @@ export default class Config extends EasCommand {
profile: maybeProfile,
'non-interactive': nonInteractive,
} = flags;
const { getDynamicPublicProjectConfigAsync, projectDir, getDynamicLoggedInAsync } =
const { getDynamicPublicProjectConfigAsync, projectDir, sessionManager } =
await this.getContextAsync(Config, {
nonInteractive,
withServerSideEnvironment: null,
});
const accessor = EasJsonAccessor.fromProjectPath(projectDir);
@@ -88,7 +88,10 @@ export default class Config extends EasCommand {
Log.log(JSON.stringify(profile, null, 2));
}
} else {
const { graphqlClient } = await getDynamicLoggedInAsync();
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
nonInteractive,
});
const graphqlClient = createGraphqlClient(authenticationInfo);
const { exp: appConfig } = await evaluateConfigWithEnvVarsAsync({
buildProfile: profile,
buildProfileName: profileName,
@@ -39,7 +39,6 @@ export default class InitializeBuildCredentials extends EasCommand {
vcsClient,
} = await this.getContextAsync(InitializeBuildCredentials, {
nonInteractive: false,
withServerSideEnvironment: null,
});
const platform = await selectPlatformAsync(flags.platform);
@@ -22,13 +22,12 @@ export default class Credentials extends EasCommand {
const { flags } = await this.parse(Credentials);
const {
loggedIn: { actor, graphqlClient },
optionalPrivateProjectConfig: privateProjectConfig,
privateProjectConfig,
getDynamicPrivateProjectConfigAsync,
analytics,
vcsClient,
} = await this.getContextAsync(Credentials, {
nonInteractive: false,
withServerSideEnvironment: null,
});
await new SelectPlatform(
@@ -15,10 +15,9 @@ export default class DeviceCreate extends EasCommand {
// this command is interactive by design
const {
loggedIn: { actor, graphqlClient },
optionalPrivateProjectConfig: privateProjectConfig,
privateProjectConfig,
} = await this.getContextAsync(DeviceCreate, {
nonInteractive: false,
withServerSideEnvironment: null,
});
const ctx = await createContextAsync({
@@ -34,7 +34,7 @@ export default class DeviceDelete extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -43,7 +43,7 @@ export default class DeviceDelete extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid } = flags;
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceDelete, {
nonInteractive: paginatedQueryOptions.nonInteractive,
+2 -2
View File
@@ -21,7 +21,7 @@ export default class BuildList extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -29,7 +29,7 @@ export default class BuildList extends EasCommand {
const { flags } = await this.parse(BuildList);
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(BuildList, {
nonInteractive: paginatedQueryOptions.nonInteractive,
@@ -35,7 +35,7 @@ export default class DeviceRename extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -44,7 +44,7 @@ export default class DeviceRename extends EasCommand {
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
let { 'apple-team-id': appleTeamIdentifier, udid, name } = flags;
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceRename, {
nonInteractive: paginatedQueryOptions.nonInteractive,
+2 -2
View File
@@ -11,7 +11,7 @@ export default class DeviceView extends EasCommand {
static override args = [{ name: 'UDID' }];
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -32,7 +32,7 @@ If you are not sure what is the UDID of the device you are looking for, run:
throw new Error('Device UDID is missing');
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(DeviceView, {
nonInteractive: true,
@@ -13,20 +13,18 @@ import { EnvironmentVariableMutation } from '../../../graphql/mutations/Environm
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import {
parseVisibility,
promptVariableEnvironmentAsync,
promptVariableNameAsync,
promptVariableTypeAsync,
promptVariableValueAsync,
} from '../../../utils/prompts';
import EnvCreate from '../create';
import EnvironmentVariableCreate from '../create';
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
jest.mock('../../../graphql/queries/AppQuery');
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../utils/prompts');
describe(EnvCreate, () => {
describe(EnvironmentVariableCreate, () => {
const graphqlClient = {} as any as ExpoGraphqlClient;
const mockConfig = {} as unknown as Config;
const variableId = 'testId';
@@ -34,10 +32,6 @@ describe(EnvCreate, () => {
beforeEach(() => {
jest.resetAllMocks();
jest
.mocked(parseVisibility)
.mockImplementation(jest.requireActual('../../../utils/prompts').parseVisibility);
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
jest
.mocked(EnvironmentVariableMutation.createForAppAsync)
@@ -66,7 +60,6 @@ describe(EnvCreate, () => {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
scope: EnvironmentVariableScope.Shared,
type: EnvironmentSecretType.String,
}));
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockImplementation(async () => []);
jest.mocked(EnvironmentVariablesQuery.sharedAsync).mockImplementation(async () => []);
@@ -74,24 +67,15 @@ describe(EnvCreate, () => {
describe('in interactive mode', () => {
it('creates a project variable', async () => {
const command = new EnvCreate(
[
'--name',
'VarName',
'--value',
'VarValue',
'--environment',
'production',
'--visibility',
'secret',
],
const command = new EnvironmentVariableCreate(
['--name', 'VarName', '--value', 'VarValue', '--environment', 'production'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
@@ -102,7 +86,7 @@ describe(EnvCreate, () => {
name: 'VarName',
value: 'VarValue',
environments: [EnvironmentVariableEnvironment.Production],
visibility: EnvironmentVariableVisibility.Secret,
visibility: EnvironmentVariableVisibility.Public,
type: EnvironmentSecretType.String,
},
testProjectId
@@ -110,24 +94,15 @@ describe(EnvCreate, () => {
});
it('updates an existing variable in the same environment', async () => {
const command = new EnvCreate(
[
'--name',
'VarName',
'--value',
'VarValue',
'--environment',
'production',
'--visibility',
'secret',
],
const command = new EnvironmentVariableCreate(
['--name', 'VarName', '--value', 'VarValue', '--environment', 'production'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
const otherVariableId = 'otherId';
@@ -167,12 +142,12 @@ describe(EnvCreate, () => {
name: 'VarName',
value: 'VarValue',
environments: [EnvironmentVariableEnvironment.Production],
visibility: EnvironmentVariableVisibility.Secret,
visibility: EnvironmentVariableVisibility.Public,
});
});
it('creates an account-wide variable', async () => {
const command = new EnvCreate(
it('creates a shared variable', async () => {
const command = new EnvironmentVariableCreate(
[
'--name',
'VarName',
@@ -181,7 +156,7 @@ describe(EnvCreate, () => {
'--environment',
'production',
'--scope',
'account',
'shared',
],
mockConfig
);
@@ -199,7 +174,6 @@ describe(EnvCreate, () => {
{
name: 'VarName',
value: 'VarValue',
isGlobal: true,
environments: [EnvironmentVariableEnvironment.Production],
visibility: EnvironmentVariableVisibility.Public,
type: EnvironmentSecretType.String,
@@ -208,8 +182,8 @@ describe(EnvCreate, () => {
);
});
it('throws if an account-wide variable already exists', async () => {
const command = new EnvCreate(
it('throws if a shared variable already exists', async () => {
const command = new EnvironmentVariableCreate(
[
'--name',
'VarName',
@@ -218,7 +192,7 @@ describe(EnvCreate, () => {
'--environment',
'production',
'--scope',
'account',
'shared',
],
mockConfig
);
@@ -243,8 +217,8 @@ describe(EnvCreate, () => {
await expect(command.runAsync()).rejects.toThrow();
});
it('updates if an account-wide variable already exists and --force flag is set', async () => {
const command = new EnvCreate(
it('updates if a shared variable already exists and --force flag is set', async () => {
const command = new EnvironmentVariableCreate(
[
'--name',
'VarName',
@@ -254,7 +228,7 @@ describe(EnvCreate, () => {
'production',
'--force',
'--scope',
'account',
'shared',
],
mockConfig
);
@@ -289,8 +263,8 @@ describe(EnvCreate, () => {
});
});
it('creates an account-wide variable and links it', async () => {
const command = new EnvCreate(
it('creates a shared variable and links it', async () => {
const command = new EnvironmentVariableCreate(
[
'--name',
'VarName',
@@ -301,7 +275,7 @@ describe(EnvCreate, () => {
'--environment',
'development',
'--scope',
'account',
'shared',
'--link',
],
mockConfig
@@ -310,7 +284,7 @@ describe(EnvCreate, () => {
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
@@ -325,7 +299,6 @@ describe(EnvCreate, () => {
EnvironmentVariableEnvironment.Development,
],
visibility: EnvironmentVariableVisibility.Public,
isGlobal: true,
type: EnvironmentSecretType.String,
},
testAccountId
@@ -345,14 +318,10 @@ describe(EnvCreate, () => {
});
it('prompts for missing arguments', async () => {
const command = new EnvCreate([], mockConfig);
const command = new EnvironmentVariableCreate([], mockConfig);
jest.mocked(promptVariableNameAsync).mockImplementation(async () => 'VarName');
jest.mocked(promptVariableValueAsync).mockImplementation(async () => 'VarValue');
jest
.mocked(promptVariableTypeAsync)
.mockImplementation(async () => EnvironmentSecretType.String);
jest
.mocked(promptVariableEnvironmentAsync)
// @ts-expect-error
@@ -361,7 +330,7 @@ describe(EnvCreate, () => {
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
@@ -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 EnvDelete from '../delete';
import EnvironmentVariableDelete from '../delete';
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
jest.mock('../../../prompts');
jest.mock('../../../log');
describe(EnvDelete, () => {
describe(EnvironmentVariableDelete, () => {
const projectId = 'test-project-id';
const variableId = '1';
const graphqlClient = {};
@@ -40,7 +40,7 @@ describe(EnvDelete, () => {
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvDelete(
const command = new EnvironmentVariableDelete(
[
'--variable-name',
'TEST_VARIABLE',
@@ -66,7 +66,7 @@ describe(EnvDelete, () => {
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvDelete([], mockConfig);
const command = new EnvironmentVariableDelete([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -82,7 +82,10 @@ describe(EnvDelete, () => {
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvDelete(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableDelete(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
@@ -96,7 +99,7 @@ describe(EnvDelete, () => {
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
const command = new EnvironmentVariableDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
@@ -113,7 +116,7 @@ describe(EnvDelete, () => {
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(false);
const command = new EnvDelete(['--non-interactive'], mockConfig);
const command = new EnvironmentVariableDelete(['--non-interactive'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
@@ -3,26 +3,24 @@ import { Config } from '@oclif/core';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
import {
EnvironmentSecretType,
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
EnvironmentVariableVisibility,
} from '../../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../../utils/prompts';
import EnvGet from '../get';
import EnvironmentVariableGet from '../get';
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
jest.mock('../../../graphql/queries/AppQuery');
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../utils/prompts');
describe(EnvGet, () => {
describe(EnvironmentVariableGet, () => {
const graphqlClient = {} as any as ExpoGraphqlClient;
const mockConfig = {} as unknown as Config;
const mockVariables: EnvironmentVariableFragment[] = [
const mockVariables = [
{
id: 'var1',
name: 'TEST_VAR_1',
@@ -32,7 +30,6 @@ describe(EnvGet, () => {
updatedAt: new Date().toISOString(),
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Public,
type: EnvironmentSecretType.String,
},
];
@@ -42,12 +39,12 @@ describe(EnvGet, () => {
.mockResolvedValueOnce(mockVariables);
jest.spyOn(Log, 'log').mockImplementation(() => {});
const command = new EnvGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
const command = new EnvironmentVariableGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
@@ -58,7 +55,6 @@ describe(EnvGet, () => {
appId: testProjectId,
environment: undefined,
filterNames: ['TEST_VAR_1'],
includeFileContent: true,
}
);
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
@@ -68,7 +64,7 @@ describe(EnvGet, () => {
const errorMessage =
"Variable name is required. Run the command with '--variable-name VARIABLE_NAME' flag";
const command = new EnvGet([], mockConfig);
const command = new EnvironmentVariableGet([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
@@ -85,7 +81,7 @@ describe(EnvGet, () => {
.mockResolvedValueOnce(EnvironmentVariableEnvironment.Production);
jest.mocked(promptVariableNameAsync).mockResolvedValueOnce('TEST_VAR_1');
const command = new EnvGet([], mockConfig);
const command = new EnvironmentVariableGet([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
@@ -103,7 +99,6 @@ describe(EnvGet, () => {
updatedAt: new Date().toISOString(),
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Public,
type: EnvironmentSecretType.String,
},
]);
await command.runAsync();
@@ -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 EnvLink from '../link';
import EnvironmentVariableLink from '../link';
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
@@ -19,13 +19,13 @@ jest.mock('../../../prompts');
jest.mock('../../../graphql/queries/AppQuery');
jest.mock('../../../log');
describe(EnvLink, () => {
describe(EnvironmentVariableLink, () => {
const projectId = 'test-project-id';
const variableId = '1';
const graphqlClient = {};
const mockConfig = {} as unknown as Config;
const mockContext = {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
};
@@ -39,7 +39,7 @@ describe(EnvLink, () => {
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
});
it('links an account-wide variable to the current project in non-interactive mode', async () => {
it('links a shared variable to the current project in non-interactive mode', async () => {
const mockVariables = [
{
id: variableId,
@@ -53,7 +53,7 @@ describe(EnvLink, () => {
mockVariables[0]
);
const command = new EnvLink(
const command = new EnvironmentVariableLink(
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
mockConfig
);
@@ -75,7 +75,7 @@ describe(EnvLink, () => {
);
});
it('links an account-wide variable to the current project to a specified environment', async () => {
it('links a shared variable to the current project to a specified environment', async () => {
const mockVariables = [
{
id: variableId,
@@ -89,7 +89,7 @@ describe(EnvLink, () => {
mockVariables[0]
);
const command = new EnvLink(
const command = new EnvironmentVariableLink(
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
mockConfig
);
@@ -137,7 +137,7 @@ describe(EnvLink, () => {
);
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvLink(
const command = new EnvironmentVariableLink(
[
'--variable-name',
'TEST_VARIABLE',
@@ -186,7 +186,7 @@ describe(EnvLink, () => {
(promptAsync as jest.Mock).mockResolvedValue({ environments: mockVariables[0].environments });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvLink([], mockConfig);
const command = new EnvironmentVariableLink([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -207,12 +207,15 @@ describe(EnvLink, () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableLink(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});
@@ -236,7 +239,7 @@ describe(EnvLink, () => {
});
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvLink([], mockConfig);
const command = new EnvironmentVariableLink([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -260,12 +263,15 @@ describe(EnvLink, () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableLink(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});
});
@@ -4,7 +4,6 @@ import { getMockAppFragment } from '../../../__tests__/commands/utils';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
import {
EnvironmentSecretType,
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
@@ -13,7 +12,7 @@ import {
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
import EnvList from '../list';
import EnvironmentVariableList from '../list';
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/queries/AppQuery');
@@ -29,7 +28,6 @@ const mockVariables: EnvironmentVariableFragment[] = [
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
type: EnvironmentSecretType.String,
},
{
id: 'var2',
@@ -40,11 +38,10 @@ const mockVariables: EnvironmentVariableFragment[] = [
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
type: EnvironmentSecretType.String,
},
];
describe(EnvList, () => {
describe(EnvironmentVariableList, () => {
const graphqlClient = {} as any as ExpoGraphqlClient;
const mockConfig = {} as unknown as Config;
@@ -56,19 +53,18 @@ describe(EnvList, () => {
it('lists project environment variables successfully', async () => {
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
const command = new EnvList([], mockConfig);
const command = new EnvironmentVariableList([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
includeFileContent: false,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
@@ -77,19 +73,18 @@ describe(EnvList, () => {
it('lists project environment variables in specified environments', async () => {
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
const command = new EnvList(['--environment', 'production'], mockConfig);
const command = new EnvironmentVariableList(['--environment', 'production'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: EnvironmentVariableEnvironment.Production,
includeFileContent: false,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
@@ -100,12 +95,12 @@ describe(EnvList, () => {
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
.mockResolvedValueOnce(mockVariables);
const command = new EnvList(['--include-sensitive'], mockConfig);
const command = new EnvironmentVariableList(['--include-sensitive'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
@@ -114,52 +109,52 @@ describe(EnvList, () => {
{
appId: testProjectId,
environment: undefined,
includeFileContent: false,
}
);
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});
it('lists account-wide environment variables successfully', async () => {
it('lists shared environment variables successfully', async () => {
jest.mocked(EnvironmentVariablesQuery.sharedAsync).mockResolvedValueOnce(mockVariables);
const command = new EnvList(['--scope', 'account'], mockConfig);
const command = new EnvironmentVariableList(['--scope', 'shared'], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
expect(EnvironmentVariablesQuery.sharedAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
includeFileContent: false,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});
it('lists account-wide environment variables including sensitive values', async () => {
it('lists shared environment variables including sensitive values', async () => {
jest
.mocked(EnvironmentVariablesQuery.sharedWithSensitiveAsync)
.mockResolvedValueOnce(mockVariables);
const command = new EnvList(['--include-sensitive', '--scope', 'account'], mockConfig);
const command = new EnvironmentVariableList(
['--include-sensitive', '--scope', 'shared'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
projectId: testProjectId,
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();
expect(EnvironmentVariablesQuery.sharedWithSensitiveAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
includeFileContent: false,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
@@ -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 EnvUnlink from '../unlink';
import EnvironmentVariableUnlink from '../unlink';
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
@@ -19,13 +19,13 @@ jest.mock('../../../prompts');
jest.mock('../../../graphql/queries/AppQuery');
jest.mock('../../../log');
describe(EnvUnlink, () => {
describe(EnvironmentVariableUnlink, () => {
const projectId = 'test-project-id';
const variableId = '1';
const graphqlClient = {};
const mockConfig = {} as unknown as Config;
const mockContext = {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
};
@@ -39,7 +39,7 @@ describe(EnvUnlink, () => {
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
});
it('unlinks an account-wide variable from the current project in non-interactive mode', async () => {
it('unlinks a shared variable from the current project in non-interactive mode', async () => {
const mockVariables = [
{
id: variableId,
@@ -54,7 +54,7 @@ describe(EnvUnlink, () => {
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
).mockResolvedValue(mockVariables[0]);
const command = new EnvUnlink(
const command = new EnvironmentVariableUnlink(
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
mockConfig
);
@@ -76,7 +76,7 @@ describe(EnvUnlink, () => {
);
});
it('unlinks an account-wide variable from the current project in a specified environment', async () => {
it('unlinks a shared variable from the current project in a specified environment', async () => {
const mockVariables = [
{
id: variableId,
@@ -91,7 +91,7 @@ describe(EnvUnlink, () => {
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
).mockResolvedValue(mockVariables[0]);
const command = new EnvUnlink(
const command = new EnvironmentVariableUnlink(
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
mockConfig
);
@@ -141,7 +141,7 @@ describe(EnvUnlink, () => {
});
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvUnlink([], mockConfig);
const command = new EnvironmentVariableUnlink([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -162,12 +162,15 @@ describe(EnvUnlink, () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableUnlink(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});
@@ -191,7 +194,7 @@ describe(EnvUnlink, () => {
});
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvUnlink([], mockConfig);
const command = new EnvironmentVariableUnlink([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -215,12 +218,15 @@ describe(EnvUnlink, () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableUnlink(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});
});
@@ -5,14 +5,13 @@ import { getMockAppFragment } from '../../../__tests__/commands/utils';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableScope,
EnvironmentVariableVisibility,
} from '../../../graphql/generated';
import { EnvironmentVariableMutation } from '../../../graphql/mutations/EnvironmentVariableMutation';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
import { promptAsync, toggleConfirmAsync } from '../../../prompts';
import EnvUpdate from '../update';
import EnvironmentVariableUpdate from '../update';
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
@@ -20,7 +19,7 @@ jest.mock('../../../prompts');
jest.mock('../../../graphql/queries/AppQuery');
jest.mock('../../../log');
describe(EnvUpdate, () => {
describe(EnvironmentVariableUpdate, () => {
const projectId = 'test-project-id';
const variableId = '1';
const graphqlClient = {};
@@ -47,7 +46,7 @@ describe(EnvUpdate, () => {
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
const command = new EnvUpdate(
const command = new EnvironmentVariableUpdate(
[
'--variable-name',
'TEST_VARIABLE',
@@ -60,8 +59,6 @@ describe(EnvUpdate, () => {
'NEW_VARIABLE',
'--environment',
'production',
'--visibility',
'plaintext',
],
mockConfig
);
@@ -74,14 +71,13 @@ describe(EnvUpdate, () => {
name: 'NEW_VARIABLE',
value: 'new-value',
environments: [EnvironmentVariableEnvironment.Production],
visibility: EnvironmentVariableVisibility.Public,
});
expect(Log.withTick).toHaveBeenCalledWith(
`Updated variable ${chalk.bold('TEST_VARIABLE')} on project @testuser/testpp.`
);
});
it('updates an account-wide variable by selected name in non-interactive mode', async () => {
it('updates a shared variable by selected name in non-interactive mode', async () => {
const mockVariables = [
{
id: variableId,
@@ -93,7 +89,7 @@ describe(EnvUpdate, () => {
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
const command = new EnvUpdate(
const command = new EnvironmentVariableUpdate(
[
'--variable-name',
'TEST_VARIABLE',
@@ -107,7 +103,7 @@ describe(EnvUpdate, () => {
'--environment',
'production',
'--scope',
'account',
'shared',
],
mockConfig
);
@@ -135,7 +131,7 @@ describe(EnvUpdate, () => {
(promptAsync as jest.Mock).mockResolvedValue({ name: mockVariables[0].name });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
const command = new EnvUpdate([], mockConfig);
const command = new EnvironmentVariableUpdate([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();
@@ -154,7 +150,10 @@ describe(EnvUpdate, () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
const command = new EnvUpdate(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
const command = new EnvironmentVariableUpdate(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
@@ -1,3 +0,0 @@
// 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 --variable-name VARIABLE_NAME flag to fix the issue"`;
@@ -0,0 +1,3 @@
// 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 --variable-name VARIABLE_NAME flag to fix the issue"`;
+54 -149
View File
@@ -1,14 +1,11 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import EasCommand from '../../commandUtils/EasCommand';
import {
EASEnvironmentVariableScopeFlag,
EASEnvironmentVariableScopeFlagValue,
EASMultiEnvironmentFlag,
EASNonInteractiveFlag,
EASVariableScopeFlag,
EASVariableVisibilityFlag,
} from '../../commandUtils/flags';
import {
@@ -26,50 +23,28 @@ import {
} from '../../project/projectUtils';
import { confirmAsync } from '../../prompts';
import {
parseVisibility,
promptVariableEnvironmentAsync,
promptVariableNameAsync,
promptVariableTypeAsync,
promptVariableValueAsync,
promptVariableVisibilityAsync,
} from '../../utils/prompts';
import { isEnvironment, performForEnvironmentsAsync } from '../../utils/variableUtils';
import { performForEnvironmentsAsync } from '../../utils/variableUtils';
interface RawCreateFlags {
type CreateFlags = {
name?: string;
value?: string;
link?: boolean;
force?: boolean;
type?: 'string' | 'file';
visibility?: 'plaintext' | 'sensitive' | 'secret';
scope: EASEnvironmentVariableScopeFlagValue;
visibility?: EnvironmentVariableVisibility;
scope?: EnvironmentVariableScope;
environment?: EnvironmentVariableEnvironment[];
'non-interactive': boolean;
}
};
interface CreateFlags {
name?: string;
value?: string;
link?: boolean;
force?: boolean;
type?: 'string' | 'file';
visibility?: 'plaintext' | 'sensitive' | 'secret';
scope: EnvironmentVariableScope;
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';
export default class EnvCreate extends EasCommand {
static override description = 'create an environment variable for the current project or account';
static override args = [
{
name: 'environment',
description:
"Environment to create the variable in. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override hidden = true;
static override flags = {
name: Flags.string({
@@ -79,33 +54,28 @@ export default class EnvCreate extends EasCommand {
description: 'Text value or the variable',
}),
link: Flags.boolean({
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
description: 'Link shared variable to the current project',
}),
force: Flags.boolean({
description: 'Overwrite existing variable',
default: false,
}),
type: Flags.enum<'string' | 'file'>({
description: 'The type of variable',
options: ['string', 'file'],
}),
...EASVariableVisibilityFlag,
...EASEnvironmentVariableScopeFlag,
...EASVariableScopeFlag,
...EASMultiEnvironmentFlag,
...EASNonInteractiveFlag,
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.Analytics,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvCreate);
const { flags } = await this.parse(EnvironmentVariableCreate);
const validatedFlags = this.sanitizeFlags(flags);
const validatedFlags = this.validateFlags(flags);
const {
name,
@@ -116,14 +86,12 @@ export default class EnvCreate extends EasCommand {
visibility,
link,
force,
type,
fileName,
} = await this.promptForMissingFlagsAsync(validatedFlags, args);
} = await this.promptForMissingFlagsAsync(validatedFlags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvCreate, {
} = await this.getContextAsync(EnvironmentVariableCreate, {
nonInteractive,
});
@@ -158,12 +126,12 @@ export default class EnvCreate extends EasCommand {
await this.promptForOverwriteAsync({
nonInteractive,
force,
message: `Account-wide variable with ${name} name already exists on this account.`,
message: `Shared variable with ${name} name already exists on this account.`,
suggestion: 'Do you want to unlink it first?',
});
Log.withTick(
`Unlinking account-wide variable ${chalk.bold(name)} on project ${chalk.bold(
`Unlinking shared variable ${chalk.bold(name)} on project ${chalk.bold(
projectDisplayName
)}.`
);
@@ -187,8 +155,6 @@ export default class EnvCreate extends EasCommand {
value,
visibility,
environments,
type,
fileName,
})
: await EnvironmentVariableMutation.createForAppAsync(
graphqlClient,
@@ -197,8 +163,7 @@ export default class EnvCreate extends EasCommand {
value,
environments,
visibility,
type: type ?? EnvironmentSecretType.String,
fileName,
type: EnvironmentSecretType.String,
},
projectId
);
@@ -227,8 +192,8 @@ export default class EnvCreate extends EasCommand {
overwrite = true;
} else {
throw new Error(
`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.`
`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.`
);
}
}
@@ -241,7 +206,6 @@ export default class EnvCreate extends EasCommand {
value,
visibility,
environments,
type,
})
: await EnvironmentVariableMutation.createSharedVariableAsync(
graphqlClient,
@@ -250,8 +214,7 @@ export default class EnvCreate 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,
type: EnvironmentSecretType.String,
},
ownerAccount.id
);
@@ -268,7 +231,7 @@ export default class EnvCreate extends EasCommand {
if (link) {
Log.withTick(
`Linking account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
`Linking shared variable ${chalk.bold(name)} to project ${chalk.bold(
projectDisplayName
)}.`
);
@@ -283,9 +246,7 @@ export default class EnvCreate extends EasCommand {
});
Log.withTick(
`Linked account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
projectDisplayName
)}.`
`Linked shared variable ${chalk.bold(name)} to project ${chalk.bold(projectDisplayName)}.`
);
}
}
@@ -316,114 +277,58 @@ export default class EnvCreate extends EasCommand {
}
}
private async promptForMissingFlagsAsync(
{
name,
value,
environment: environments,
visibility,
'non-interactive': nonInteractive,
type,
...rest
}: CreateFlags,
{ environment }: { environment?: string }
): Promise<
Required<
Omit<CreateFlags, 'type' | 'visibility'> & {
type: EnvironmentSecretType | undefined;
visibility: EnvironmentVariableVisibility;
}
> & { fileName?: string }
> {
private async promptForMissingFlagsAsync({
name,
value,
environment,
visibility = EnvironmentVariableVisibility.Public,
'non-interactive': nonInteractive,
...rest
}: CreateFlags): Promise<Required<CreateFlags>> {
if (!name) {
name = await promptVariableNameAsync(nonInteractive);
}
let newType;
let newVisibility = visibility ? parseVisibility(visibility) : undefined;
if (type === 'file') {
newType = EnvironmentSecretType.FileBase64;
} else if (type === 'string') {
newType = EnvironmentSecretType.String;
}
if (!type && !value && !nonInteractive) {
newType = await promptVariableTypeAsync(nonInteractive);
}
if (!newVisibility) {
newVisibility = await promptVariableVisibilityAsync(nonInteractive);
}
if (!value) {
value = await promptVariableValueAsync({
nonInteractive,
hidden: newVisibility !== EnvironmentVariableVisibility.Public,
filePath: newType === EnvironmentSecretType.FileBase64,
hidden: visibility !== EnvironmentVariableVisibility.Public,
});
}
let environmentFilePath: string | undefined;
let fileName: string | undefined;
if (newType === EnvironmentSecretType.FileBase64) {
environmentFilePath = path.resolve(value);
if (!(await fs.pathExists(environmentFilePath))) {
throw new Error(`File "${value}" does not exist`);
}
fileName = path.basename(environmentFilePath);
if (!environment) {
environment = await promptVariableEnvironmentAsync({ nonInteractive, multiple: true });
}
value = environmentFilePath ? await fs.readFile(environmentFilePath, 'base64') : value;
if (environment && !isEnvironment(environment.toUpperCase())) {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
let newEnvironments = environments
? environments
: environment
? [environment.toUpperCase() as EnvironmentVariableEnvironment]
: undefined;
if (!newEnvironments) {
newEnvironments = await promptVariableEnvironmentAsync({ nonInteractive, multiple: true });
if (!newEnvironments || newEnvironments.length === 0) {
throw new Error('No environments selected');
}
}
newVisibility = newVisibility ?? EnvironmentVariableVisibility.Public;
return {
name,
value,
environment: newEnvironments,
visibility: newVisibility,
environment,
visibility,
link: rest.link ?? false,
force: rest.force ?? false,
scope: rest.scope ?? EnvironmentVariableScope.Project,
'non-interactive': nonInteractive,
type: newType,
fileName,
...rest,
};
}
private sanitizeFlags(flags: RawCreateFlags): CreateFlags {
if (flags.scope !== 'account' && flags.link) {
private validateFlags(flags: CreateFlags): CreateFlags {
if (flags.scope !== EnvironmentVariableScope.Shared && flags.link) {
throw new Error(
`Unexpected argument: --link can only be used when creating account-wide variables`
`Unexpected argument: --link can only be used when creating shared variables`
);
}
return {
...flags,
scope:
flags.scope === 'account'
? EnvironmentVariableScope.Shared
: EnvironmentVariableScope.Project,
};
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;
}
}
+16 -50
View File
@@ -4,9 +4,8 @@ import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import {
EASEnvironmentVariableScopeFlag,
EASEnvironmentVariableScopeFlagValue,
EASNonInteractiveFlag,
EASVariableScopeFlag,
EasEnvironmentFlagParameters,
} from '../../commandUtils/flags';
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
@@ -14,24 +13,19 @@ import { EnvironmentVariableMutation } from '../../graphql/mutations/Environment
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptAsync, toggleConfirmAsync } from '../../prompts';
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
import { formatVariableName } from '../../utils/variableUtils';
interface DeleteFlags {
type DeleteFlags = {
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
scope: EnvironmentVariableScope;
}
scope?: EnvironmentVariableScope;
};
interface RawDeleteFlags {
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
scope: EASEnvironmentVariableScopeFlagValue;
}
export default class EnvironmentVariableDelete extends EasCommand {
static override description = 'delete an environment variable by name';
export default class EnvDelete extends EasCommand {
static override description = 'delete an environment variable for the current project or account';
static override hidden = true;
static override flags = {
'variable-name': Flags.string({
@@ -41,36 +35,27 @@ export default class EnvDelete extends EasCommand {
...EasEnvironmentFlagParameters,
description: 'Current environment of the variable to delete',
}),
...EASEnvironmentVariableScopeFlag,
...EASVariableScopeFlag,
...EASNonInteractiveFlag,
};
static override args = [
{
name: 'environment',
description:
"Current environment of the variable to delete. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvDelete);
const { flags } = await this.parse(EnvironmentVariableDelete);
const {
'variable-name': name,
'variable-environment': environment,
'non-interactive': nonInteractive,
scope,
} = this.sanitizeInputs(flags, args);
} = this.validateFlags(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvDelete, {
} = await this.getContextAsync(EnvironmentVariableDelete, {
nonInteractive,
});
@@ -145,10 +130,7 @@ export default class EnvDelete extends EasCommand {
Log.withTick(`️Deleted variable ${selectedVariable.name}".`);
}
private sanitizeInputs(
flags: RawDeleteFlags,
{ environment }: { environment?: string }
): DeleteFlags {
private validateFlags(flags: DeleteFlags): DeleteFlags {
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error(
@@ -159,22 +141,6 @@ export default class EnvDelete extends EasCommand {
}
}
const scope =
flags.scope === 'account'
? EnvironmentVariableScope.Shared
: EnvironmentVariableScope.Project;
if (environment) {
environment = environment.toUpperCase();
if (!isEnvironment(environment)) {
throw new Error(
"Invalid environment. Use one of 'production', 'preview', or 'development'."
);
}
return { ...flags, 'variable-environment': environment, scope };
}
return { ...flags, scope };
return flags;
}
}
+49 -49
View File
@@ -3,63 +3,61 @@ import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { isEnvironment } from '../../utils/variableUtils';
type ParsedFlags =
| {
nonInteractive: true;
environment: EnvironmentVariableEnvironment;
command: string;
}
| {
nonInteractive: false;
environment?: EnvironmentVariableEnvironment;
command: string;
};
interface RawFlags {
environment?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
}
export default class EnvExec extends EasCommand {
static override description =
'execute a command with environment variables from the selected environment';
'execute a bash command with environment variables from the selected environment';
static override hidden = true;
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
static override flags = {
...EASEnvironmentFlag,
...EASNonInteractiveFlag,
};
static override args = [
{
name: 'environment',
required: true,
description:
"Environment to execute the command in. One of 'production', 'preview', or 'development'.",
},
{
name: 'bash_command',
name: 'BASH_COMMAND',
required: true,
description: 'bash command to execute with the environment variables from the environment',
},
];
async runAsync(): Promise<void> {
const { flags, args } = await this.parse(EnvExec);
const {
flags,
args: { BASH_COMMAND: command },
} = await this.parse(EnvExec);
const parsedFlags = this.sanitizeFlagsAndArgs(flags, args);
const parsedFlags = this.sanitizeFlags(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvExec, {
nonInteractive: parsedFlags.nonInteractive,
@@ -74,32 +72,25 @@ export default class EnvExec extends EasCommand {
environment,
});
await this.runCommandWithEnvVarsAsync({
command: parsedFlags.command,
environmentVariables,
});
await this.runCommandWithEnvVarsAsync({ command, environmentVariables });
}
private sanitizeFlagsAndArgs(
rawFlags: RawFlags,
{ bash_command, environment }: Record<string, string>
): ParsedFlags {
if (rawFlags['non-interactive'] && (!bash_command || !environment)) {
throw new Error(
"You must specify both environment and bash command when running in non-interactive mode. Run command as `eas env:exec ENVIRONMENT 'bash command'`."
);
private sanitizeFlags(rawFlags: RawFlags): ParsedFlags {
const environment = rawFlags.environment;
if (rawFlags['non-interactive']) {
if (!environment) {
throw new Error(
'You must specify an environment when running in non-interactive mode. Use the --environment flag.'
);
}
return {
nonInteractive: true,
environment,
};
}
environment = environment?.toUpperCase();
if (!isEnvironment(environment)) {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
return {
nonInteractive: rawFlags['non-interactive'],
environment,
command: bash_command,
nonInteractive: false,
environment: rawFlags.environment,
};
}
@@ -158,20 +149,29 @@ 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) {
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.`
);
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}`);
}
Log.newLine();
+36 -71
View File
@@ -1,59 +1,43 @@
import { Flags } from '@oclif/core';
import assert from 'assert';
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 {
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
EnvironmentVariableVisibility,
} from '../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../utils/prompts';
import { formatVariable, formatVariableValue, isEnvironment } from '../../utils/variableUtils';
import { formatVariable } from '../../utils/variableUtils';
interface RawGetFlags {
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
format?: string;
scope: EASEnvironmentVariableScopeFlagValue;
}
interface GetFlags {
type GetFlags = {
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
format?: string;
scope: EnvironmentVariableScope;
}
};
export default class EnvGet extends EasCommand {
static override description = 'view an environment variable for the current project or account';
export default class EnvironmentVariableGet extends EasCommand {
static override description = 'get environment variable';
static override hidden = true;
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
static override args = [
{
name: 'environment',
description:
"Current environment of the variable. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override flags = {
'variable-name': Flags.string({
description: 'Name of the variable',
@@ -63,12 +47,12 @@ export default class EnvGet extends EasCommand {
description: 'Current environment of the variable',
}),
...EASVariableFormatFlag,
...EASEnvironmentVariableScopeFlag,
...EASVariableScopeFlag,
...EASNonInteractiveFlag,
};
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvGet);
const { flags } = await this.parse(EnvironmentVariableGet);
let {
'variable-environment': environment,
@@ -76,12 +60,12 @@ export default class EnvGet extends EasCommand {
'non-interactive': nonInteractive,
format,
scope,
} = this.sanitizeInputs(flags, args);
} = this.validateFlags(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvGet, {
} = await this.getContextAsync(EnvironmentVariableGet, {
nonInteractive,
});
@@ -89,13 +73,6 @@ export default class EnvGet extends EasCommand {
name = await promptVariableNameAsync(nonInteractive);
}
if (!environment) {
environment = await promptVariableEnvironmentAsync({
nonInteractive,
multiple: false,
});
}
const variables = await getVariablesAsync(graphqlClient, scope, projectId, name, environment);
if (variables.length === 0) {
@@ -106,11 +83,24 @@ export default class EnvGet extends EasCommand {
let variable;
if (variables.length > 1) {
if (!environment) {
const availableEnvironments = variables.reduce<EnvironmentVariableEnvironment[]>(
(acc, v) => [...acc, ...(v.environments ?? [])],
[] as EnvironmentVariableEnvironment[]
);
environment = await promptVariableEnvironmentAsync({
nonInteractive,
multiple: false,
availableEnvironments,
});
}
assert(environment, 'Environment is required.');
const variableInEnvironment = variables.find(v => v.environments?.includes(environment!));
if (!variableInEnvironment) {
throw new Error(
`Variable with name "${name}" not found in environment "${environment.toLocaleLowerCase()}"`
);
throw new Error(`Variable with name "${name}" not found in environment "${environment}"`);
}
variable = variableInEnvironment;
@@ -118,7 +108,7 @@ export default class EnvGet extends EasCommand {
variable = variables[0];
}
if (variable.visibility === EnvironmentVariableVisibility.Secret) {
if (!variable.value) {
throw new Error(
`${chalk.bold(
variable.name
@@ -127,13 +117,13 @@ export default class EnvGet extends EasCommand {
}
if (format === 'short') {
Log.log(`${chalk.bold(variable.name)}=${formatVariableValue(variable)}`);
Log.log(`${chalk.bold(variable.name)}=${variable.value}`);
} else {
Log.log(formatVariable(variable));
}
}
private sanitizeInputs(flags: RawGetFlags, { environment }: { environment?: string }): GetFlags {
private validateFlags(flags: GetFlags): GetFlags {
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error('Variable name is required. Run the command with --variable-name flag.');
@@ -141,34 +131,11 @@ export default class EnvGet extends EasCommand {
if (!flags.scope) {
throw new Error('Scope is required. Run the command with --scope flag.');
}
if (!(flags['variable-environment'] ?? environment)) {
if (!flags['variable-environment']) {
throw new Error('Environment is required.');
}
}
if (environment && flags['variable-environment']) {
throw new Error(
"You can't use both --variable-environment flag when environment is passed as an argument. Run `eas env:get --help` for more information."
);
}
const scope =
flags.scope === 'account'
? EnvironmentVariableScope.Shared
: EnvironmentVariableScope.Project;
if (environment) {
environment = environment.toUpperCase();
if (!isEnvironment(environment)) {
throw new Error(
"Invalid environment. Use one of 'production', 'preview', or 'development'."
);
}
return {
...flags,
'variable-environment': environment,
scope,
};
}
return { ...flags, scope };
return flags;
}
}
@@ -189,7 +156,6 @@ async function getVariablesAsync(
appId: projectId,
environment,
filterNames: [name],
includeFileContent: true,
});
return appVariables;
} else {
@@ -198,7 +164,6 @@ async function getVariablesAsync(
{
appId: projectId,
filterNames: [name],
includeFileContent: true,
}
);
return sharedVariables;
+15 -66
View File
@@ -14,19 +14,11 @@ import Log from '../../log';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
import { formatVariableName } from '../../utils/variableUtils';
type LinkFlags = {
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
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 = {
@@ -41,34 +33,24 @@ export default class EnvLink extends EasCommand {
...EASNonInteractiveFlag,
};
static override args = [
{
name: 'environment',
description:
"Environment to pull variables from. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvLink);
let {
'variable-name': name,
'variable-environment': currentEnvironment,
'non-interactive': nonInteractive,
environment: environments,
} = this.validateInputs(flags, args);
flags: {
'variable-name': name,
'variable-environment': currentEnvironment,
'non-interactive': nonInteractive,
environment: environments,
},
} = await this.parse(EnvironmentVariableLink);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvLink, {
} = await this.getContextAsync(EnvironmentVariableLink, {
nonInteractive,
});
@@ -82,13 +64,8 @@ export default class EnvLink extends EasCommand {
let selectedVariable = variables[0];
if (variables.length > 1) {
if (nonInteractive) {
throw new Error(
'Multiple variables found, run command with --variable-name and --variable-environment arguments.'
);
}
selectedVariable = await selectAsync(
'Select account-wide variable',
'Select shared variable',
variables.map(variable => ({
title: formatVariableName(variable),
value: variable,
@@ -97,7 +74,7 @@ export default class EnvLink extends EasCommand {
}
if (!selectedVariable) {
throw new Error(`Account-wide variable ${name} doesn't exist`);
throw new Error(`Shared variable ${name} doesn't exist`);
}
let explicitSelect = false;
@@ -173,32 +150,4 @@ export default class EnvLink extends EasCommand {
}
}
}
private validateInputs(flags: LinkFlags, { environment }: { environment?: string }): LinkFlags {
environment = environment?.toUpperCase();
if (environment && !isEnvironment(environment)) {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
const environments = flags.environment
? flags.environment
: environment
? [environment as EnvironmentVariableEnvironment]
: undefined;
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error(
`Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with ${chalk.bold(
'--variable-name VARIABLE_NAME'
)} flag to fix the issue`
);
}
}
return {
...flags,
environment: environments,
};
}
}
+34 -104
View File
@@ -4,53 +4,44 @@ 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 {
EnvironmentVariableWithFileContent,
EnvironmentVariablesQuery,
} from '../../graphql/queries/EnvironmentVariablesQuery';
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
} from '../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import {
formatVariable,
formatVariableValue,
isEnvironment,
performForEnvironmentsAsync,
} from '../../utils/variableUtils';
import { formatVariable, performForEnvironmentsAsync } from '../../utils/variableUtils';
async function getVariablesForScopeAsync(
graphqlClient: ExpoGraphqlClient,
{
scope,
includingSensitive,
includeFileContent,
environment,
projectId,
}: {
scope: EnvironmentVariableScope;
includingSensitive: boolean;
includeFileContent: boolean;
environment?: EnvironmentVariableEnvironment;
projectId: string;
}
): Promise<EnvironmentVariableWithFileContent[]> {
): Promise<EnvironmentVariableFragment[]> {
if (scope === EnvironmentVariableScope.Project) {
if (includingSensitive) {
return await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
appId: projectId,
environment,
includeFileContent,
});
}
return await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
includeFileContent,
});
}
@@ -58,38 +49,17 @@ async function getVariablesForScopeAsync(
? await EnvironmentVariablesQuery.sharedWithSensitiveAsync(graphqlClient, {
appId: projectId,
environment,
includeFileContent,
})
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
appId: projectId,
environment,
includeFileContent,
});
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId, environment });
}
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;
}
export default class EnvList extends EasCommand {
static override description = 'list environment variables for the current project or account';
static override hidden = true;
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
@@ -98,40 +68,25 @@ export default class EnvList extends EasCommand {
description: 'Display sensitive values in the output',
default: false,
}),
'include-file-content': Flags.boolean({
description: 'Display files content in the output',
default: false,
}),
...EASMultiEnvironmentFlag,
...EASVariableFormatFlag,
...EASEnvironmentVariableScopeFlag,
...EASVariableScopeFlag,
...EASMultiEnvironmentFlag,
};
static override args = [
{
name: 'environment',
description:
"Environment to list the variables from. One of 'production', 'preview', or 'development'.",
required: false,
},
];
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvList);
let {
format,
environment: environments,
scope,
'include-sensitive': includeSensitive,
'include-file-content': includeFileContent,
'non-interactive': nonInteractive,
} = this.sanitizeInputs(flags, args);
flags: {
environment: environments,
format,
scope,
'include-sensitive': includeSensitive,
'non-interactive': nonInteractive,
},
} = await this.parse(EnvironmentValueList);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvList, {
} = await this.getContextAsync(EnvironmentValueList, {
nonInteractive: true,
});
@@ -143,28 +98,28 @@ export default class EnvList extends EasCommand {
const variables = await getVariablesForScopeAsync(graphqlClient, {
scope,
includingSensitive: includeSensitive,
includeFileContent,
environment,
projectId,
});
Log.addNewLineIfNone();
if (environment) {
Log.log(chalk.bold(`Environment: ${environment.toLocaleLowerCase()}`));
}
if (variables.length === 0) {
Log.log('No variables found for this environment.');
return;
Log.log(chalk.bold(`Environment: ${environment}`));
}
if (format === 'short') {
for (const variable of variables) {
Log.log(`${chalk.bold(variable.name)}=${formatVariableValue(variable)}`);
// TODO: Add Learn more link
Log.log(
`${chalk.bold(variable.name)}=${
variable.value ??
"***** (This is a secret env variable that can only be accessed on EAS builder and can't be read in any UI. Learn more.)"
}`
);
}
} else {
if (scope === EnvironmentVariableScope.Shared) {
Log.log(chalk.bold('Account-wide variables for this account:'));
Log.log(chalk.bold('Shared variables for this account:'));
} else {
Log.log(chalk.bold(`Variables for this project:`));
}
@@ -174,29 +129,4 @@ export default class EnvList extends EasCommand {
}
});
}
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'.");
}
const environments = flags.environment
? flags.environment
: environment
? [environment.toUpperCase() as EnvironmentVariableEnvironment]
: undefined;
return {
...flags,
'non-interactive': flags['non-interactive'] ?? false,
environment: environments,
scope:
flags.scope === 'account'
? EnvironmentVariableScope.Shared
: EnvironmentVariableScope.Project,
};
}
}
+31 -87
View File
@@ -1,42 +1,27 @@
import { Flags } from '@oclif/core';
import dotenv from 'dotenv';
import * as fs from 'fs-extra';
import path from 'path';
import EasCommand from '../../commandUtils/EasCommand';
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
import { EnvironmentSecretType, EnvironmentVariableVisibility } from '../../graphql/generated';
import {
EnvironmentVariableWithFileContent,
EnvironmentVariablesQuery,
} from '../../graphql/queries/EnvironmentVariablesQuery';
import { EnvironmentVariableFragment } from '../../graphql/generated';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { confirmAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { isEnvironment } from '../../utils/variableUtils';
export default class EnvPull extends EasCommand {
static override description =
'pull environment variables for the selected environment to .env file';
export default class EnvironmentVariablePull extends EasCommand {
static override description = 'pull env file';
static override hidden = true;
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.ProjectDir,
};
static override args = [
{
name: 'environment',
description:
"Environment to pull variables from. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override flags = {
...EASNonInteractiveFlag,
...EASEnvironmentFlag,
...EASNonInteractiveFlag,
path: Flags.string({
description: 'Path to the result `.env` file',
default: '.env.local',
@@ -45,25 +30,16 @@ export default class EnvPull extends EasCommand {
async runAsync(): Promise<void> {
let {
args: { environment: argEnvironment },
flags: { environment: flagEnvironment, path: targetPath, 'non-interactive': nonInteractive },
} = await this.parse(EnvPull);
let environment = flagEnvironment?.toUpperCase() ?? argEnvironment?.toUpperCase();
flags: { environment, path: targetPath, 'non-interactive': nonInteractive },
} = await this.parse(EnvironmentVariablePull);
if (!environment) {
environment = await promptVariableEnvironmentAsync({ nonInteractive });
}
if (!isEnvironment(environment)) {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
projectDir,
} = await this.getContextAsync(EnvPull, {
} = await this.getContextAsync(EnvironmentVariablePull, {
nonInteractive,
});
@@ -74,7 +50,6 @@ export default class EnvPull extends EasCommand {
{
appId: projectId,
environment,
includeFileContent: true,
}
);
@@ -88,63 +63,32 @@ export default class EnvPull extends EasCommand {
}
}
let currentEnvLocal: Record<string, string> = {};
const filePrefix = `# Environment: ${environment}\n\n`;
if (await fs.exists(targetPath)) {
currentEnvLocal = dotenv.parse(await fs.readFile(targetPath, 'utf8'));
}
const filePrefix = `# Environment: ${environment.toLocaleLowerCase()}\n\n`;
const isFileVariablePresent = environmentVariables.some(v => {
return v.type === EnvironmentSecretType.FileBase64 && v.valueWithFileContent;
});
const envDir = path.join(projectDir, '.eas', '.env');
if (isFileVariablePresent) {
await fs.mkdir(envDir, { recursive: true });
}
const skippedSecretVariables: string[] = [];
const overridenSecretVariables: string[] = [];
const envFileContentLines = await Promise.all(
environmentVariables.map(async (variable: EnvironmentVariableWithFileContent) => {
if (variable.visibility === EnvironmentVariableVisibility.Secret) {
if (currentEnvLocal[variable.name]) {
overridenSecretVariables.push(variable.name);
return `${variable.name}=${currentEnvLocal[variable.name]}`;
}
skippedSecretVariables.push(variable.name);
return `# ${variable.name}=***** (secret)`;
}
if (variable.type === EnvironmentSecretType.FileBase64 && variable.valueWithFileContent) {
const filePath = path.join(envDir, variable.name);
await fs.writeFile(filePath, variable.valueWithFileContent, 'base64');
return `${variable.name}=${filePath}`;
const envFileContent = environmentVariables
.map((variable: EnvironmentVariableFragment) => {
if (variable.value === null) {
return `# ${variable.name}=***** (secret variables are not available for reading)`;
}
return `${variable.name}=${variable.value}`;
})
);
.join('\n');
await fs.writeFile(targetPath, filePrefix + envFileContent);
await fs.writeFile(targetPath, filePrefix + envFileContentLines.join('\n'));
const secretEnvVariables = environmentVariables.filter(
(variable: EnvironmentVariableFragment) => variable.value === null
);
if (secretEnvVariables.length > 0) {
Log.warn(
`The eas env:pull command tried to pull environment variables with "secret" visibility. The variables with "secret" visibility are not available for reading, therefore thet were marked as "*****" in the generated .env file. Provide values for these manually in ${targetPath} if needed. Skipped variables: ${secretEnvVariables
.map(v => v.name)
.join('\n')}`
);
Log.warn();
}
Log.log(
`Pulled plain text and sensitive environment variables from "${environment.toLowerCase()}" environment to ${targetPath}.`
`Pulled 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')}.`);
}
if (skippedSecretVariables.length > 0) {
Log.addNewLineIfNone();
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(
', '
)}.`
);
}
}
}
+113 -160
View File
@@ -1,10 +1,9 @@
import { Flags } from '@oclif/core';
import dotenv from 'dotenv';
import fs from 'fs-extra';
import path from 'path';
import EasCommand from '../../commandUtils/EasCommand';
import { EASMultiEnvironmentFlag } from '../../commandUtils/flags';
import { EASEnvironmentFlag } from '../../commandUtils/flags';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
@@ -18,168 +17,145 @@ import {
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { confirmAsync, promptAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { isEnvironment } from '../../utils/variableUtils';
export default class EnvPush extends EasCommand {
static override description =
'push environment variables from .env file to the selected environment';
type PushFlags = {
environment?: EnvironmentVariableEnvironment;
path: string;
};
export default class EnvironmentVariablePush extends EasCommand {
static override description = 'push env file';
static override hidden = true;
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
static override flags = {
...EASMultiEnvironmentFlag,
...EASEnvironmentFlag,
path: Flags.string({
description: 'Path to the input `.env` file',
default: '.env.local',
}),
};
static override args = [
{
name: 'environment',
description:
"Environment to push variables to. One of 'production', 'preview', or 'development'.",
required: false,
},
];
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvPush);
let { environment: environments, path: envPath } = this.parseFlagsAndArgs(flags, args);
const { flags } = await this.parse(EnvironmentVariablePush);
const { environment, path: envPath } = this.validateFlags(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvPush, {
} = await this.getContextAsync(EnvironmentVariablePush, {
nonInteractive: false,
});
if (!environments) {
environments = await promptVariableEnvironmentAsync({
nonInteractive: false,
multiple: true,
});
}
const updateVariables: Record<string, EnvironmentVariablePushInput> =
await this.parseEnvFileAsync(envPath, environments);
await this.parseEnvFileAsync(envPath, environment);
const variableNames = Object.keys(updateVariables);
for (const environment of environments) {
const displayedEnvironment = environment.toLocaleLowerCase();
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
filterNames: variableNames,
});
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
filterNames: variableNames,
});
const existingDifferentVariables: EnvironmentVariableFragment[] = [];
// Remove variables that are the same as the ones in the environment
existingVariables.forEach(existingVariable => {
const existingVariableUpdate = updateVariables[existingVariable.name];
const existingDifferentVariables: EnvironmentVariableFragment[] = [];
// Remove variables that are the same as the ones in the environment
existingVariables.forEach(variable => {
const existingVariableUpdate = updateVariables[variable.name];
if (existingVariableUpdate && existingVariableUpdate.value !== variable.value) {
existingDifferentVariables.push(variable);
} else {
delete updateVariables[variable.name];
}
});
if (existingVariableUpdate) {
const hasMoreEnvironments = existingVariableUpdate.environments.some(
newEnv => !existingVariable.environments?.includes(newEnv)
);
const existingDifferentSharedVariables = existingDifferentVariables.filter(
variable => variable.scope === EnvironmentVariableScope.Shared
);
if (existingVariableUpdate.value !== existingVariable.value || hasMoreEnvironments) {
existingDifferentVariables.push(existingVariable);
} else {
delete updateVariables[existingVariable.name];
}
}
});
const existingDifferentSharedVariables = existingDifferentVariables.filter(
variable => variable.scope === EnvironmentVariableScope.Shared
if (existingDifferentSharedVariables.length > 0) {
const existingDifferentSharedVariablesNames = existingDifferentSharedVariables.map(
variable => variable.name
);
Log.error('Shared 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');
}
if (existingDifferentSharedVariables.length > 0) {
const existingDifferentSharedVariablesNames = existingDifferentSharedVariables.map(
variable => variable.name
);
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('Account-wide variables cannot be overwritten by eas env:push command');
if (existingDifferentVariables.length > 0) {
Log.warn('Some variables already exist in the environment.');
const variableNames = existingDifferentVariables.map(variable => variable.name);
const confirmationMessage =
variableNames.length > 1
? `The ${variableNames.join(
', '
)} environment variables already exist in ${environment} environment. Do you want to override them all?`
: `The ${variableNames[0]} environment variable already exists in ${environment} environment. Do you want to override it?`;
const confirm = await confirmAsync({
message: confirmationMessage,
});
let variablesToOverwrite: string[] = [];
if (!confirm && existingDifferentVariables.length === 1) {
throw new Error('No new variables to push.');
}
if (existingDifferentVariables.length > 0) {
Log.warn(`Some variables already exist in the ${displayedEnvironment} environment.`);
const variableNames = existingDifferentVariables.map(variable => variable.name);
const confirmationMessage =
variableNames.length > 1
? `The ${variableNames.join(
', '
)} environment variables already exist in ${displayedEnvironment} environment. Do you want to override them all?`
: `The ${variableNames[0]} environment variable already exists in ${displayedEnvironment} environment. Do you want to override it?`;
const confirm = await confirmAsync({
message: confirmationMessage,
if (confirm) {
variablesToOverwrite = existingDifferentVariables.map(variable => variable.name);
} else {
const promptResult = await promptAsync({
type: 'multiselect',
name: 'variablesToOverwrite',
message: 'Select variables to overwrite:',
// @ts-expect-error property missing from `@types/prompts`
optionsPerPage: 20,
choices: existingDifferentVariables.map(variable => ({
title: `${variable.name}: ${updateVariables[variable.name].value} (was ${
variable.value ?? '(secret)'
})`,
value: variable.name,
})),
});
variablesToOverwrite = promptResult.variablesToOverwrite;
}
let variablesToOverwrite: string[] = [];
if (!confirm && existingDifferentVariables.length === 0) {
throw new Error('No new variables to push.');
}
if (confirm) {
variablesToOverwrite = existingDifferentVariables.map(variable => variable.name);
for (const existingVariable of existingVariables) {
const name = existingVariable.name;
if (variablesToOverwrite.includes(name)) {
updateVariables[name]['overwrite'] = true;
} else {
const promptResult = await promptAsync({
type: 'multiselect',
name: 'variablesToOverwrite',
message: 'Select variables to overwrite:',
// @ts-expect-error property missing from `@types/prompts`
optionsPerPage: 20,
choices: existingDifferentVariables.map(variable => ({
title: `${variable.name}: ${updateVariables[variable.name].value} (was ${
variable.value ?? '(secret)'
})`,
value: variable.name,
})),
});
variablesToOverwrite = promptResult.variablesToOverwrite;
}
for (const existingVariable of existingVariables) {
const name = existingVariable.name;
if (variablesToOverwrite.includes(name)) {
updateVariables[name]['overwrite'] = true;
} else {
delete updateVariables[name];
}
delete updateVariables[name];
}
}
}
// Check if any of the sensitive variables already exist in the environment. Prompt the user to overwrite them.
const existingSensitiveVariables = existingVariables.filter(
variable => variable.visibility !== EnvironmentVariableVisibility.Public
// Check if any of the sensitive variables already exist in the environment. Prompt the user to overwrite them.
const existingSensitiveVariables = existingVariables.filter(
variable => variable.value === null
);
if (existingSensitiveVariables.length > 0) {
const existingSensitiveVariablesNames = existingSensitiveVariables.map(
variable => `- ${variable.name}`
);
if (existingSensitiveVariables.length > 0) {
const existingSensitiveVariablesNames = existingSensitiveVariables.map(
variable => `- ${variable.name}`
);
const confirm = await confirmAsync({
message: `You are about to overwrite sensitive variables.\n${existingSensitiveVariablesNames.join(
'\n'
)}\n Do you want to continue?`,
});
if (!confirm) {
throw new Error('Aborting...');
}
const confirm = await confirmAsync({
message: `You are about to overwrite sensitive variables.\n${existingSensitiveVariablesNames.join(
'\n'
)}\n Do you want to continue?`,
});
if (!confirm) {
throw new Error('Aborting...');
}
}
@@ -195,30 +171,13 @@ export default class EnvPush extends EasCommand {
variablesToPush,
projectId
);
Log.log(`Uploaded env file to ${environments.join(', ').toLocaleLowerCase()}.`);
}
parseFlagsAndArgs(
flags: { path: string; environment: EnvironmentVariableEnvironment[] | undefined },
{ environment }: Record<string, string>
): { environment?: EnvironmentVariableEnvironment[]; path: string } {
if (environment && !isEnvironment(environment.toUpperCase())) {
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
}
const environments =
flags.environment ??
(environment ? [environment.toUpperCase() as EnvironmentVariableEnvironment] : undefined);
return {
...flags,
environment: environments,
};
Log.log(`Uploaded env file to ${environment} environment.`);
}
private async parseEnvFileAsync(
envPath: string,
environments: EnvironmentVariableEnvironment[]
environment: string
): Promise<Record<string, EnvironmentVariablePushInput>> {
if (!(await fs.exists(envPath))) {
throw new Error(`File ${envPath} does not exist.`);
@@ -227,25 +186,11 @@ export default class EnvPush extends EasCommand {
const variables: Record<string, string> = dotenv.parse(await fs.readFile(envPath, 'utf8'));
const hasFileVariables = Object.values(variables).some(value =>
value.includes(path.join('.eas', '.env'))
);
if (hasFileVariables) {
Log.warn('File variables are not supported in this command.');
}
for (const [name, value] of Object.entries(variables)) {
// Skip file variables
const fileVariablePath = path.join('.eas', '.env', name);
if (value.endsWith(fileVariablePath)) {
Log.warn(`Skipping file variable ${name}`);
continue;
}
pushInput[name] = {
name,
value,
environments,
environment,
visibility: name.startsWith('EXPO_SENSITIVE')
? EnvironmentVariableVisibility.Sensitive
: EnvironmentVariableVisibility.Public,
@@ -254,4 +199,12 @@ export default class EnvPush extends EasCommand {
return pushInput;
}
private validateFlags(flags: PushFlags): Required<PushFlags> {
if (!flags.environment) {
throw new Error('Please provide an environment to push the env file to.');
}
return { ...flags, environment: flags.environment };
}
}
+14 -60
View File
@@ -10,19 +10,11 @@ import Log from '../../log';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
import { formatVariableName } from '../../utils/variableUtils';
type UnlinkFlags = {
'variable-name'?: string;
'non-interactive': boolean;
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 = {
@@ -34,32 +26,22 @@ export default class EnvUnlink extends EasCommand {
};
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
static override args = [
{
name: 'environment',
description:
"Environment to unlink the variable from. One of 'production', 'preview', or 'development'.",
required: false,
},
];
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvUnlink);
let {
'variable-name': name,
'non-interactive': nonInteractive,
environment: unlinkEnvironments,
} = this.validateInputs(flags, args);
flags: {
'variable-name': name,
'non-interactive': nonInteractive,
environment: unlinkEnvironments,
},
} = await this.parse(EnvironmentVariableUnlink);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvUnlink, {
} = await this.getContextAsync(EnvironmentVariableUnlink, {
nonInteractive,
});
@@ -76,7 +58,7 @@ export default class EnvUnlink extends EasCommand {
throw new Error("Multiple variables found, please select one using '--variable-name'");
}
selectedVariable = await selectAsync(
'Select account-wide variable',
'Select shared variable',
variables.map(variable => ({
title: formatVariableName(variable),
value: variable,
@@ -85,7 +67,7 @@ export default class EnvUnlink extends EasCommand {
}
if (!selectedVariable) {
throw new Error(`Account-wide variable ${name} doesn't exist`);
throw new Error(`Shared variable ${name} doesn't exist`);
}
let explicitSelect = false;
@@ -161,32 +143,4 @@ export default class EnvUnlink extends EasCommand {
}
}
}
private validateInputs(
flags: UnlinkFlags,
{ environment }: { environment?: string }
): UnlinkFlags {
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error(
'Current name is required in non-interactive mode. Run the command with --variable-name flag.'
);
}
}
if (environment) {
environment = environment.toUpperCase();
if (!isEnvironment(environment)) {
throw new Error(
"Invalid environment. Use one of 'production', 'preview', or 'development'."
);
}
return {
environment: [environment],
...flags,
};
}
return flags;
}
}
+43 -197
View File
@@ -1,20 +1,16 @@
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import EasCommand from '../../commandUtils/EasCommand';
import {
EASEnvironmentVariableScopeFlag,
EASEnvironmentVariableScopeFlagValue,
EASMultiEnvironmentFlag,
EASNonInteractiveFlag,
EASVariableScopeFlag,
EASVariableVisibilityFlag,
EasEnvironmentFlagParameters,
} from '../../commandUtils/flags';
import {
EnvironmentSecretType,
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
@@ -29,41 +25,29 @@ import {
} from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import {
parseVisibility,
promptVariableEnvironmentAsync,
promptVariableNameAsync,
promptVariableTypeAsync,
promptVariableValueAsync,
promptVariableVisibilityAsync,
} from '../../utils/prompts';
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
import { formatVariableName } from '../../utils/variableUtils';
interface RawUpdateFlags {
type UpdateFlags = {
name?: string;
value?: string;
scope: EASEnvironmentVariableScopeFlagValue;
scope?: EnvironmentVariableScope;
environment?: EnvironmentVariableEnvironment[];
visibility?: 'plaintext' | 'sensitive' | 'secret';
type?: 'string' | 'file';
visibility?: EnvironmentVariableVisibility;
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
}
};
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;
}
export default class EnvironmentVariableUpdate extends EasCommand {
static override description =
'update an environment variable on the current project or owner account';
export default class EnvUpdate extends EasCommand {
static override description = 'update an environment variable on the current project or account';
static override hidden = true;
static override flags = {
'variable-name': Flags.string({
@@ -79,49 +63,35 @@ export default class EnvUpdate extends EasCommand {
value: Flags.string({
description: 'New value or the variable',
}),
type: Flags.enum<'string' | 'file'>({
description: 'The type of variable',
options: ['string', 'file'],
}),
...EASVariableVisibilityFlag,
...EASEnvironmentVariableScopeFlag,
...EASVariableScopeFlag,
...EASMultiEnvironmentFlag,
...EASNonInteractiveFlag,
};
static override args = [
{
name: 'environment',
description:
"Current environment of the variable to update. One of 'production', 'preview', or 'development'.",
required: false,
},
];
static override contextDefinition = {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.Analytics,
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { args, flags } = await this.parse(EnvUpdate);
const {
const { flags } = await this.parse(EnvironmentVariableUpdate);
let {
name,
value: rawValue,
value,
scope,
'variable-name': currentName,
'variable-environment': currentEnvironment,
'non-interactive': nonInteractive,
environment: environments,
type,
visibility,
} = this.sanitizeInputs(flags, args);
} = this.validateFlags(flags);
const {
projectId,
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvUpdate, {
} = await this.getContextAsync(EnvironmentVariableUpdate, {
nonInteractive,
});
@@ -156,7 +126,7 @@ export default class EnvUpdate extends EasCommand {
if (existingVariables.length === 0) {
throw new Error(
`Variable with name ${currentName} ${
currentEnvironment ? `in environment ${currentEnvironment.toLocaleLowerCase()}` : ''
currentEnvironment ? `in environment ${currentEnvironment}` : ''
} does not exist ${suffix}.`
);
} else if (existingVariables.length > 1) {
@@ -172,187 +142,63 @@ export default class EnvUpdate extends EasCommand {
}
assert(selectedVariable, 'Variable must be selected');
const {
name: newName,
value: newValue,
environment: newEnvironments,
visibility: newVisibility,
type: newType,
fileName,
} = await this.promptForMissingFlagsAsync(selectedVariable, {
name,
value: rawValue,
environment: environments,
visibility,
'non-interactive': nonInteractive,
type,
scope,
});
const variable = await EnvironmentVariableMutation.updateAsync(graphqlClient, {
id: selectedVariable.id,
name: newName,
value: newValue,
environments: newEnvironments,
type: newType,
visibility: newVisibility,
fileName: newValue ? fileName : undefined,
});
if (!variable) {
throw new Error(`Could not update variable with name ${name} ${suffix}`);
}
Log.withTick(`Updated variable ${chalk.bold(selectedVariable.name)} ${suffix}.`);
}
private sanitizeInputs(
flags: RawUpdateFlags,
{ environment }: { environment?: string }
): UpdateFlags {
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error(
'Current name is required in non-interactive mode. Run the command with --variable-name flag.'
);
}
if (flags['type'] && !flags['value']) {
throw new Error('Value is required when type is set. Run the command with --value flag.');
}
}
const scope =
flags.scope === 'account'
? EnvironmentVariableScope.Shared
: EnvironmentVariableScope.Project;
if (environment) {
environment = environment.toUpperCase();
if (!isEnvironment(environment)) {
throw new Error(
"Invalid environment. Use one of 'production', 'preview', or 'development'."
);
}
return {
...flags,
'variable-environment': environment,
scope,
};
}
return { ...flags, scope };
}
private async promptForMissingFlagsAsync(
selectedVariable: EnvironmentVariableFragment,
{
name,
value,
environment: environments,
visibility,
'non-interactive': nonInteractive,
type,
...rest
}: UpdateFlags
): Promise<
Omit<UpdateFlags, 'type' | 'visibility'> & {
type?: EnvironmentSecretType;
visibility?: EnvironmentVariableVisibility;
fileName?: string;
}
> {
let newType;
let newVisibility: EnvironmentVariableVisibility | undefined;
let fileName: string | undefined;
if (type === 'file') {
newType = EnvironmentSecretType.FileBase64;
} else if (type === 'string') {
newType = EnvironmentSecretType.String;
}
if (!nonInteractive) {
if (!name) {
name = await promptVariableNameAsync(nonInteractive, selectedVariable.name);
if (!name || name.length === 0) {
name = undefined;
}
}
if (!type && !value && !nonInteractive) {
newType = await promptVariableTypeAsync(nonInteractive, selectedVariable.type);
}
if (!value) {
value = await promptVariableValueAsync({
nonInteractive,
required: false,
filePath: (newType ?? selectedVariable.type) === EnvironmentSecretType.FileBase64,
initial:
(newType ?? selectedVariable.type) === EnvironmentSecretType.FileBase64
? undefined
: selectedVariable.value,
initial: selectedVariable.value,
});
if (!value || value.length === 0 || value === selectedVariable.value) {
if (!value || value.length === 0) {
value = undefined;
newType = undefined;
}
}
let environmentFilePath: string | undefined;
if ((newType ?? selectedVariable.type) === EnvironmentSecretType.FileBase64 && value) {
environmentFilePath = path.resolve(value);
if (!(await fs.pathExists(environmentFilePath))) {
throw new Error(`File "${value}" does not exist`);
}
fileName = path.basename(environmentFilePath);
}
value = environmentFilePath ? await fs.readFile(environmentFilePath, 'base64') : value;
if (!environments || environments.length === 0) {
environments = await promptVariableEnvironmentAsync({
nonInteractive,
multiple: true,
selectedEnvironments: selectedVariable.environments ?? [],
});
if (
!environments ||
environments.length === 0 ||
environments === selectedVariable.environments
) {
environments = undefined;
}
}
if (!visibility) {
newVisibility = await promptVariableVisibilityAsync(
visibility = await promptVariableVisibilityAsync(
nonInteractive,
selectedVariable.visibility
);
if (!newVisibility || newVisibility === selectedVariable.visibility) {
newVisibility = undefined;
}
}
}
if (visibility) {
newVisibility = parseVisibility(visibility);
}
return {
...rest,
const variable = await EnvironmentVariableMutation.updateAsync(graphqlClient, {
id: selectedVariable.id,
name,
value,
environment: environments,
visibility: newVisibility,
'non-interactive': nonInteractive,
type: newType,
fileName,
};
environments,
visibility,
});
if (!variable) {
throw new Error(`Could not update variable with name ${name} ${suffix}`);
}
Log.withTick(`Updated variable ${chalk.bold(selectedVariable.name)} ${suffix}.`);
}
private validateFlags(flags: UpdateFlags): UpdateFlags {
if (flags['non-interactive']) {
if (!flags['variable-name']) {
throw new Error(
'Current name is required in non-interactive mode. Run the command with --variable-name flag.'
);
}
}
return flags;
}
}
@@ -1,378 +0,0 @@
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;
}
}
@@ -41,7 +41,6 @@ export default class MetadataPull extends EasCommand {
vcsClient,
} = await this.getContextAsync(MetadataPull, {
nonInteractive: false,
withServerSideEnvironment: null,
});
// this command is interactive (all nonInteractive flags passed to utility functions are false)
@@ -39,7 +39,6 @@ export default class MetadataPush extends EasCommand {
vcsClient,
} = await this.getContextAsync(MetadataPush, {
nonInteractive: false,
withServerSideEnvironment: null,
});
// this command is interactive (all nonInteractive flags passed to utility functions are false)
Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff. Show more