mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
140
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7b7eacd4e | ||
|
|
ec43906005 | ||
|
|
b71f324c83 | ||
|
|
4bb550e3bb | ||
|
|
6a55c65908 | ||
|
|
ab3495da5e | ||
|
|
63b67635e0 | ||
|
|
4b5cda3bbc | ||
|
|
63e3c1f2ab | ||
|
|
3f1543989f | ||
|
|
1c964d2ab1 | ||
|
|
bc282b6262 | ||
|
|
17661d1cf0 | ||
|
|
8d35745124 | ||
|
|
3740ee4ef9 | ||
|
|
751d3d1287 | ||
|
|
1fd14c07e1 | ||
|
|
39ea1705d8 | ||
|
|
2c96b9929d | ||
|
|
0dae24ae47 | ||
|
|
a63efe01a1 | ||
|
|
803457b37a | ||
|
|
dcb1305501 | ||
|
|
97fe2d62d2 | ||
|
|
ca0850b82b | ||
|
|
039a7b408b | ||
|
|
68660ae845 | ||
|
|
34bb2c8a75 | ||
|
|
ac5cf3d7dd | ||
|
|
8d686b855b | ||
|
|
19973bb86f | ||
|
|
d132ac09de | ||
|
|
4c5e5d4ded | ||
|
|
44af16efe1 | ||
|
|
f34487cc8e | ||
|
|
7ea1c5de17 | ||
|
|
261f611770 | ||
|
|
f9337f419a | ||
|
|
4280e10836 | ||
|
|
a2fe99c97e | ||
|
|
868fd399a4 | ||
|
|
7d60ba2cea | ||
|
|
db7461a569 | ||
|
|
facffbdcd7 | ||
|
|
dbdefa0e3d | ||
|
|
66daeade84 | ||
|
|
48ebd9bb78 | ||
|
|
3d3574d351 | ||
|
|
8da6adf4da | ||
|
|
bd2d39f26b | ||
|
|
e16e1f3871 | ||
|
|
d8bd6a1548 | ||
|
|
0f2fad3a65 | ||
|
|
78e05c48c8 | ||
|
|
fa10404843 | ||
|
|
5bf2c3a65b | ||
|
|
ec71e1168f | ||
|
|
8bf28029dc | ||
|
|
fc3c1e73a8 | ||
|
|
bb3915dc7e | ||
|
|
753df2c428 | ||
|
|
3111492f83 | ||
|
|
9c95755a46 | ||
|
|
da727ba170 | ||
|
|
4571910c4d | ||
|
|
c7cfcafff8 | ||
|
|
6f913f7320 | ||
|
|
0a49296231 | ||
|
|
7fc09b2376 | ||
|
|
fed28a87be | ||
|
|
073bdcd575 | ||
|
|
6979c8d67b | ||
|
|
0869bc511f | ||
|
|
693562d742 | ||
|
|
734a071345 | ||
|
|
26fd7a7daa | ||
|
|
df13e0433e | ||
|
|
fa72248d45 | ||
|
|
2b51db72db | ||
|
|
79696394be | ||
|
|
86c99efdcb | ||
|
|
ee500109f6 | ||
|
|
7eff738efe | ||
|
|
fdb167df34 | ||
|
|
027d3629a6 | ||
|
|
c30c1cd5cf | ||
|
|
a5d3ea8aba | ||
|
|
37f53e0976 | ||
|
|
97a26dce40 | ||
|
|
b0b14788dd | ||
|
|
b81c60e8a4 | ||
|
|
d47b80a125 | ||
|
|
3f2dcc7e09 | ||
|
|
b039da04e2 | ||
|
|
9e55fcbe7c | ||
|
|
281ae7bad6 | ||
|
|
433c47b221 | ||
|
|
5be97c50ae | ||
|
|
2bc0e968cf | ||
|
|
a426164e85 | ||
|
|
ac799bbf76 | ||
|
|
cf8e715cd5 | ||
|
|
71ca181895 | ||
|
|
7c9970d279 | ||
|
|
f40484ec4d | ||
|
|
746d64ac30 | ||
|
|
3782767f9a | ||
|
|
efb1c87fe3 | ||
|
|
c4075b9fd0 | ||
|
|
5bd3760b57 | ||
|
|
dd190869aa | ||
|
|
c7b2b8388c | ||
|
|
3e6cf78760 | ||
|
|
5478814cf6 | ||
|
|
6449ea7c16 | ||
|
|
88740682cd | ||
|
|
8c05a711fc | ||
|
|
d0f01cb58f | ||
|
|
a212662003 | ||
|
|
3139124daf | ||
|
|
8c4be1c556 | ||
|
|
3a7dfb0001 | ||
|
|
214cff043c | ||
|
|
0515c9a6fa | ||
|
|
367b763adf | ||
|
|
49dba20200 | ||
|
|
f0b14bf6a1 | ||
|
|
161acbb160 | ||
|
|
5aad520c23 | ||
|
|
d6ec3ae106 | ||
|
|
feb8d2d8f4 | ||
|
|
c07fd222d9 | ||
|
|
1873804c8b | ||
|
|
1cc4fd8880 | ||
|
|
f83d7e0be7 | ||
|
|
b225ba4bf3 | ||
|
|
4fb5972f18 | ||
|
|
463f73eec2 | ||
|
|
2dbaed5b2a | ||
|
|
20b76f72b0 |
No files matched your search
+9
-12
@@ -1,23 +1,20 @@
|
||||
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: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/update/**']
|
||||
mentions: ['EvanBacon', 'byCedric', 'wschurman']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/src/commands/channel/**']
|
||||
mentions: ['wschurman', 'quinlanj']
|
||||
- patterns: ['packages/eas-cli/schema/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-cli/src/commands/metadata/**']
|
||||
mentions: ['bycedric']
|
||||
- patterns: ['packages/eas-json/**']
|
||||
mentions: ['szdziedzic', 'khamilowicz', 'sjchmiela']
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: tobyhs/codemention@v1.2.0
|
||||
- uses: tobyhs/codemention@v1.4.0
|
||||
with:
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
+167
@@ -8,15 +8,182 @@ 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
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "12.5.3",
|
||||
"version": "14.2.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+430
-162
@@ -81,6 +81,8 @@ 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)
|
||||
@@ -92,6 +94,14 @@ eas --help COMMAND
|
||||
* [`eas device:rename`](#eas-devicerename)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas diagnostics`](#eas-diagnostics)
|
||||
* [`eas env:create [ENVIRONMENT]`](#eas-envcreate-environment)
|
||||
* [`eas env:delete [ENVIRONMENT]`](#eas-envdelete-environment)
|
||||
* [`eas env:exec ENVIRONMENT BASH_COMMAND`](#eas-envexec-environment-bash_command)
|
||||
* [`eas env:get [ENVIRONMENT]`](#eas-envget-environment)
|
||||
* [`eas env:list [ENVIRONMENT]`](#eas-envlist-environment)
|
||||
* [`eas env:pull [ENVIRONMENT]`](#eas-envpull-environment)
|
||||
* [`eas env:push [ENVIRONMENT]`](#eas-envpush-environment)
|
||||
* [`eas env:update [ENVIRONMENT]`](#eas-envupdate-environment)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas init`](#eas-init)
|
||||
* [`eas init:onboarding [TARGET_PROJECT_DIRECTORY]`](#eas-initonboarding-target_project_directory)
|
||||
@@ -105,14 +115,11 @@ 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)
|
||||
@@ -124,6 +131,9 @@ eas --help COMMAND
|
||||
* [`eas webhook:update`](#eas-webhookupdate)
|
||||
* [`eas webhook:view ID`](#eas-webhookview-id)
|
||||
* [`eas whoami`](#eas-whoami)
|
||||
* [`eas workflow:create [NAME]`](#eas-workflowcreate-name)
|
||||
* [`eas workflow:run [FILE]`](#eas-workflowrun-file)
|
||||
* [`eas workflow:validate PATH`](#eas-workflowvalidate-path)
|
||||
|
||||
## `eas account:login`
|
||||
|
||||
@@ -143,7 +153,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -160,7 +170,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -177,7 +187,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -191,7 +201,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -243,7 +253,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -264,7 +274,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -284,7 +294,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -304,7 +314,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -327,7 +337,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -364,7 +374,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -383,7 +393,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -400,7 +410,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -419,7 +429,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -454,7 +464,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -504,7 +514,7 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -533,7 +543,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -559,7 +569,7 @@ DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -609,7 +619,7 @@ DESCRIPTION
|
||||
get the latest version from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -628,7 +638,7 @@ DESCRIPTION
|
||||
update version of an app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -647,7 +657,7 @@ DESCRIPTION
|
||||
update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -664,7 +674,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -685,7 +695,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -707,7 +717,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -727,7 +737,51 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
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)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -760,7 +814,7 @@ DESCRIPTION
|
||||
Roll a new branch out on a channel incrementally.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -783,7 +837,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -804,7 +858,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -821,7 +875,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -839,7 +893,7 @@ DESCRIPTION
|
||||
Set up credentials for building your project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -853,7 +907,7 @@ DESCRIPTION
|
||||
register new Apple Devices to use for internal distribution
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -873,7 +927,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -894,7 +948,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -915,7 +969,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -929,7 +983,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -943,7 +997,206 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
create an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:create [ENVIRONMENT] [--name <value>] [--value <value>] [--force] [--type string|file] [--visibility
|
||||
plaintext|sensitive|secret] [--scope project|account] [--environment development|preview|production]
|
||||
[--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to create the variable in. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--force Overwrite existing variable
|
||||
--name=<value> Name of the variable
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--type=(string|file) The type of variable
|
||||
--value=<value> Text value or the variable
|
||||
--visibility=(plaintext|sensitive|secret) Visibility of the variable
|
||||
|
||||
DESCRIPTION
|
||||
create an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
delete an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:delete [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--scope project|account] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable to delete. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable to delete
|
||||
--variable-name=<value> Name of the variable to delete
|
||||
|
||||
DESCRIPTION
|
||||
delete an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
execute a command with environment variables from the selected environment
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:exec ENVIRONMENT BASH_COMMAND [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to execute the command in. One of 'production', 'preview', or 'development'.
|
||||
BASH_COMMAND bash command to execute with the environment variables from the environment
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
execute a command with environment variables from the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
view an environment variable for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:get [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--format long|short] [--scope project|account] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--format=(long|short) [default: short] Output format
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable
|
||||
--variable-name=<value> Name of the variable
|
||||
|
||||
DESCRIPTION
|
||||
view an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
list environment variables for the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:list [ENVIRONMENT] [--include-sensitive] [--include-file-content] [--environment
|
||||
development|preview|production] [--format long|short] [--scope project|account]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to list the variables from. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--format=(long|short) [default: short] Output format
|
||||
--include-file-content Display files content in the output
|
||||
--include-sensitive Display sensitive values in the output
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
|
||||
DESCRIPTION
|
||||
list environment variables for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
pull environment variables for the selected environment to .env file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:pull [ENVIRONMENT] [--non-interactive] [--environment development|preview|production] [--path
|
||||
<value>]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to pull variables from. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production) Environment variable's environment
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--path=<value> [default: .env.local] Path to the result `.env` file
|
||||
|
||||
DESCRIPTION
|
||||
pull environment variables for the selected environment to .env file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
push environment variables from .env file to the selected environment
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:push [ENVIRONMENT] [--environment development|preview|production] [--path <value>]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Environment to push variables to. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--path=<value> [default: .env.local] Path to the input `.env` file
|
||||
|
||||
DESCRIPTION
|
||||
push environment variables from .env file to the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
update an environment variable on the current project or account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas env:update [ENVIRONMENT] [--variable-name <value>] [--variable-environment
|
||||
development|preview|production] [--name <value>] [--value <value>] [--type string|file] [--visibility
|
||||
plaintext|sensitive|secret] [--scope project|account] [--environment development|preview|production]
|
||||
[--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ENVIRONMENT Current environment of the variable to update. One of 'production', 'preview', or 'development'.
|
||||
|
||||
FLAGS
|
||||
--environment=(development|preview|production)... Environment variable's environment
|
||||
--name=<value> New name of the variable
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--scope=(project|account) [default: project] Scope for the variable
|
||||
--type=(string|file) The type of variable
|
||||
--value=<value> New value or the variable
|
||||
--variable-environment=(development|preview|production) Current environment of the variable to update
|
||||
--variable-name=<value> Current name of the variable
|
||||
--visibility=(plaintext|sensitive|secret) Visibility of the variable
|
||||
|
||||
DESCRIPTION
|
||||
update an environment variable on the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1051,7 +1304,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1068,7 +1321,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1085,7 +1338,7 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1115,7 +1368,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1129,7 +1382,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1152,7 +1405,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1170,82 +1423,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v12.5.3/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.3/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.3/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.3/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.3/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1276,7 +1454,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1286,35 +1464,39 @@ publish an update group
|
||||
USAGE
|
||||
$ eas update [--branch <value>] [--channel <value>] [-m <value>] [--input-dir <value>] [--skip-bundler]
|
||||
[--clear-cache] [--emit-metadata] [--rollout-percentage <value>] [-p android|ios|all] [--auto] [--private-key-path
|
||||
<value>] [--json --non-interactive]
|
||||
<value>] [--environment development|preview|production] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-m, --message=<value> A short message describing the update
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--clear-cache Clear the bundler cache before publishing
|
||||
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed information about
|
||||
the generated updates
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the certificate in
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--rollout-percentage=<value> Percentage of users this update should be immediately available to. Users not in the
|
||||
rollout will be served the previous latest update on the branch, even if that update
|
||||
is itself being rolled out. The specified number must be an integer between 1 and
|
||||
100. When not specified, this defaults to 100.
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
-m, --message=<value> A short message describing the update
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and
|
||||
update message
|
||||
--branch=<value> Branch to publish the update group on
|
||||
--channel=<value> Channel that the published update should affect
|
||||
--clear-cache Clear the bundler cache before publishing
|
||||
--emit-metadata Emit "eas-update-metadata.json" in the bundle folder with detailed
|
||||
information about the generated updates
|
||||
--environment=(development|preview|production) Environment to use for the server-side defined EAS environment
|
||||
variables during command execution.
|
||||
--input-dir=<value> [default: dist] Location of the bundle
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--private-key-path=<value> File containing the PEM-encoded private key corresponding to the
|
||||
certificate in expo-updates' configuration. Defaults to a file named
|
||||
"private-key.pem" in the certificate's directory. Only relevant if you
|
||||
are using code signing: https://docs.expo.dev/eas-update/code-signing/
|
||||
--rollout-percentage=<value> Percentage of users this update should be immediately available to.
|
||||
Users not in the rollout will be served the previous latest update on
|
||||
the branch, even if that update is itself being rolled out. The
|
||||
specified number must be an integer between 1 and 100. When not
|
||||
specified, this defaults to 100.
|
||||
--skip-bundler Skip running Expo CLI to bundle the app before publishing
|
||||
|
||||
DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1322,17 +1504,19 @@ configure the project to support EAS Update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:configure [-p android|ios|all] [--non-interactive]
|
||||
$ eas update:configure [-p android|ios|all] [--environment development|preview|production] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all] Platform to configure
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
-p, --platform=(android|ios|all) [default: all] Platform to configure
|
||||
--environment=(development|preview|production) Environment to use for the server-side defined EAS environment
|
||||
variables during command execution.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1353,7 +1537,31 @@ 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/v12.5.3/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
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)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1375,7 +1583,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1407,7 +1615,7 @@ DESCRIPTION
|
||||
roll back to an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1415,12 +1623,11 @@ roll back to the embedded update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:roll-back-to-embedded [--branch <value>] [--channel <value>] [--message <value>] [-p android|ios|all] [--auto]
|
||||
[--private-key-path <value>] [--json --non-interactive]
|
||||
$ eas update:roll-back-to-embedded [--branch <value>] [--channel <value>] [--runtime-version <value>] [--message <value>] [-p
|
||||
android|ios|all] [--private-key-path <value>] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=(android|ios|all) [default: all]
|
||||
--auto Use the current git branch and commit message for the EAS branch and update message
|
||||
--branch=<value> Branch to publish the rollback to embedded update group on
|
||||
--channel=<value> Channel that the published rollback to embedded update should affect
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
@@ -1430,12 +1637,13 @@ FLAGS
|
||||
expo-updates' configuration. Defaults to a file named "private-key.pem" in the
|
||||
certificate's directory. Only relevant if you are using code signing:
|
||||
https://docs.expo.dev/eas-update/code-signing/
|
||||
--runtime-version=<value> Runtime version that the rollback to embedded update should target
|
||||
|
||||
DESCRIPTION
|
||||
roll back to the embedded update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1455,7 +1663,7 @@ DESCRIPTION
|
||||
roll back to an embedded update or an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1475,7 +1683,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1496,7 +1704,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1516,7 +1724,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1534,7 +1742,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1556,7 +1764,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1573,7 +1781,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v12.5.3/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -1589,4 +1797,64 @@ DESCRIPTION
|
||||
ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
create a new workflow configuration YAML file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:create [NAME] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the workflow file (must end with .yml or .yaml)
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
create a new workflow configuration YAML file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:run [FILE]`
|
||||
|
||||
Run an EAS workflow
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:run [FILE] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
FILE Path to the workflow file to run
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
Run an EAS workflow
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
validate a workflow configuration yaml file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:validate PATH [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
PATH Path to the workflow configuration YAML file (must end with .yml or .yaml)
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
validate a workflow configuration yaml file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v14.2.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+3774
-542
File diff suppressed because it is too large.
Load diff
@@ -1,35 +1,34 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "12.5.3",
|
||||
"version": "14.2.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@expo/apple-utils": "1.8.0",
|
||||
"@expo/apple-utils": "2.1.5",
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@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.2",
|
||||
"@expo/env": "^0.3.0",
|
||||
"@expo/json-file": "8.2.37",
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/eas-build-job": "1.0.156",
|
||||
"@expo/eas-json": "14.2.0",
|
||||
"@expo/env": "^0.4.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "1.0.117",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
"@expo/osascript": "2.0.33",
|
||||
"@expo/package-manager": "1.1.2",
|
||||
"@expo/pkcs12": "0.0.8",
|
||||
"@expo/plist": "0.0.20",
|
||||
"@expo/multipart-body-parser": "2.0.0",
|
||||
"@expo/osascript": "2.1.4",
|
||||
"@expo/package-manager": "1.6.1",
|
||||
"@expo/pkcs12": "0.1.3",
|
||||
"@expo/plist": "0.2.0",
|
||||
"@expo/plugin-help": "5.1.23",
|
||||
"@expo/plugin-warn-if-update-available": "2.5.1",
|
||||
"@expo/prebuild-config": "6.7.3",
|
||||
"@expo/prebuild-config": "8.0.17",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.0",
|
||||
"@expo/steps": "1.0.136",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "1.0.156",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
@@ -42,6 +41,7 @@
|
||||
"chalk": "4.1.2",
|
||||
"cli-progress": "3.12.0",
|
||||
"dateformat": "4.6.3",
|
||||
"diff": "7.0.0",
|
||||
"dotenv": "16.3.1",
|
||||
"env-paths": "2.2.0",
|
||||
"envinfo": "7.11.0",
|
||||
@@ -65,7 +65,7 @@
|
||||
"mime": "3.0.0",
|
||||
"minimatch": "5.1.2",
|
||||
"minizlib": "3.0.1",
|
||||
"nanoid": "3.3.4",
|
||||
"nanoid": "3.3.8",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-forge": "1.3.1",
|
||||
"nullthrows": "1.1.1",
|
||||
@@ -87,6 +87,7 @@
|
||||
"untildify": "4.0.0",
|
||||
"uuid": "9.0.1",
|
||||
"wrap-ansi": "7.0.0",
|
||||
"yaml": "2.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -96,6 +97,7 @@
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@types/cli-progress": "3.11.5",
|
||||
"@types/dateformat": "3.0.1",
|
||||
"@types/diff": "6.0.0",
|
||||
"@types/envinfo": "7.8.3",
|
||||
"@types/express": "4.17.15",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
@@ -177,16 +179,22 @@
|
||||
"secret": {
|
||||
"description": "manage project and account secrets"
|
||||
},
|
||||
"env": {
|
||||
"description": "manage project and account environment variables"
|
||||
},
|
||||
"update": {
|
||||
"description": "manage individual updates"
|
||||
},
|
||||
"webhook": {
|
||||
"description": "manage webhooks"
|
||||
},
|
||||
"workflow": {
|
||||
"description": "manage workflows"
|
||||
}
|
||||
},
|
||||
"warn-if-update-available": {
|
||||
"timeoutInDays": 0.5,
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run ') %><%= chalk.dim(chalk.bold('npm install -g eas-cli')) %><%= chalk.dim('.') %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
"message": "<%= chalk('★') %> <%= chalk.bold(config.name + '@' + latest) %> <%= chalk.bold('is now available.') %>\n<%= chalk.dim('To upgrade, run:') %>\n<%= chalk.dim(chalk.bold('npm install -g eas-cli')) %>\n<%= chalk.dim('Proceeding with outdated version.') %>\n"
|
||||
},
|
||||
"update": {
|
||||
"node": {
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
{
|
||||
"$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": {
|
||||
@@ -323,7 +337,8 @@
|
||||
"gambling",
|
||||
"unrestrictedWebAccess",
|
||||
"kidsAgeBand",
|
||||
"seventeenPlus"
|
||||
"ageRatingOverride",
|
||||
"koreaAgeRatingOverride"
|
||||
],
|
||||
"defaultSnippets": [
|
||||
{
|
||||
@@ -345,7 +360,9 @@
|
||||
"gambling": false,
|
||||
"unrestrictedWebAccess": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
"ageRatingOverride": "NONE",
|
||||
"koreaAgeRatingOverride": "NONE",
|
||||
"lootBox": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -367,7 +384,9 @@
|
||||
"unrestrictedWebAccess": false,
|
||||
"gambling": false,
|
||||
"kidsAgeBand": null,
|
||||
"seventeenPlus": false
|
||||
"ageRatingOverride": "${13|NONE,SEVENTEEN_PLUS,UNRATED|}",
|
||||
"koreaAgeRatingOverride": "${14|NONE,FIFTEEN_PLUS,NINETEEN_PLUS|}",
|
||||
"lootBox": false
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -442,8 +461,24 @@
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -565,6 +600,20 @@
|
||||
"maxLength": 4000
|
||||
}
|
||||
}
|
||||
},
|
||||
"AppleAgeRatingOverride": {
|
||||
"enum": [
|
||||
"NONE",
|
||||
"SEVENTEEN_PLUS",
|
||||
"UNRATED"
|
||||
]
|
||||
},
|
||||
"AppleKoreaAgeRatingOverride": {
|
||||
"enum": [
|
||||
"NONE",
|
||||
"FIFTEEN_PLUS",
|
||||
"NINETEEN_PLUS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AppleConfig": {
|
||||
@@ -775,19 +824,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AppVersionSource, EasJson } from '@expo/eas-json';
|
||||
import { Command, Config } from '@oclif/core';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { BranchNotFoundError } from './utils';
|
||||
@@ -8,10 +9,11 @@ import { withErrorHandlingAsync } from '../graphql/client';
|
||||
import {
|
||||
CreateUpdateBranchForAppMutation,
|
||||
CreateUpdateBranchForAppMutationVariables,
|
||||
UpdateBranch,
|
||||
UpdateBranchBasicInfoFragment,
|
||||
UpdateBranchFragment,
|
||||
} from '../graphql/generated';
|
||||
import { BranchQuery } from '../graphql/queries/BranchQuery';
|
||||
import { UpdateBranchBasicInfoFragmentNode } from '../graphql/types/UpdateBranchBasicInfo';
|
||||
import Log from '../log';
|
||||
import { formatBranch, getBranchDescription } from '../update/utils';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -126,7 +128,7 @@ function renderPageOfBranches(
|
||||
export async function createUpdateBranchOnAppAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ appId, name }: CreateUpdateBranchForAppMutationVariables
|
||||
): Promise<Pick<UpdateBranch, 'id' | 'name'>> {
|
||||
): Promise<UpdateBranchBasicInfoFragment> {
|
||||
const result = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateUpdateBranchForAppMutation, CreateUpdateBranchForAppMutationVariables>(
|
||||
@@ -135,15 +137,17 @@ export async function createUpdateBranchOnAppAsync(
|
||||
updateBranch {
|
||||
createUpdateBranchForApp(appId: $appId, name: $name) {
|
||||
id
|
||||
name
|
||||
...UpdateBranchBasicInfoFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(UpdateBranchBasicInfoFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appId,
|
||||
name,
|
||||
}
|
||||
},
|
||||
{ additionalTypenames: ['UpdateBranch'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
@@ -163,22 +167,21 @@ export async function ensureBranchExistsAsync(
|
||||
appId: string;
|
||||
branchName: string;
|
||||
}
|
||||
): Promise<{ branchId: string; createdBranch: boolean }> {
|
||||
): Promise<{ branch: UpdateBranchBasicInfoFragment; createdBranch: boolean }> {
|
||||
try {
|
||||
const updateBranch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
|
||||
const { id } = updateBranch;
|
||||
return { branchId: id, createdBranch: false };
|
||||
return { branch: updateBranch, createdBranch: false };
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newUpdateBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
appId,
|
||||
name: branchName,
|
||||
});
|
||||
return { branchId: newUpdateBranch.id, createdBranch: true };
|
||||
return { branch: newUpdateBranch, createdBranch: true };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string> {
|
||||
return (
|
||||
(await vcsClient.getBranchNameAsync()) || `branch-${Math.random().toString(36).substring(2, 4)}`
|
||||
);
|
||||
export async function getDefaultBranchNameAsync(vcsClient: Client): Promise<string | null> {
|
||||
return await vcsClient.getBranchNameAsync();
|
||||
}
|
||||
|
||||
export class BranchNotFoundError extends Error {
|
||||
|
||||
@@ -115,6 +115,10 @@ 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;
|
||||
|
||||
@@ -20,7 +20,11 @@ import { getNextVersionCode } from '../../project/android/versions';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
import {
|
||||
bumpAppVersionAsync,
|
||||
ensureStaticConfigExists,
|
||||
getVersionConfigTarget,
|
||||
} from '../utils/version';
|
||||
|
||||
export enum BumpStrategy {
|
||||
APP_VERSION,
|
||||
@@ -53,9 +57,11 @@ export async function bumpVersionAsync({
|
||||
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const version = versionGetter(exp);
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
version: exp.version,
|
||||
version,
|
||||
versionCode: exp.android?.versionCode,
|
||||
});
|
||||
Log.log('Synchronized versions with build gradle');
|
||||
@@ -79,7 +85,7 @@ export async function bumpVersionInAppJsonAsync({
|
||||
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0';
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.ANDROID });
|
||||
} else {
|
||||
const versionCode = AndroidConfig.Version.getVersionCode(exp);
|
||||
const bumpedVersionCode = getNextVersionCode(versionCode);
|
||||
@@ -118,9 +124,11 @@ export async function maybeResolveVersionsAsync(
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const appVersion = versionGetter(exp);
|
||||
return {
|
||||
appBuildVersion: String(AndroidConfig.Version.getVersionCode(exp)),
|
||||
appVersion: exp.version,
|
||||
appVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -202,6 +210,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
applicationId
|
||||
);
|
||||
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID });
|
||||
const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient);
|
||||
let currentBuildVersion: string;
|
||||
if (remoteVersions?.buildVersion) {
|
||||
@@ -230,7 +239,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
|
||||
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: currentBuildVersion,
|
||||
runtimeVersion:
|
||||
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
|
||||
@@ -254,7 +263,7 @@ export async function resolveRemoteVersionCodeAsync(
|
||||
appId: projectId,
|
||||
platform: AppPlatform.Android,
|
||||
applicationIdentifier: applicationId,
|
||||
storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0',
|
||||
storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: String(nextBuildVersion),
|
||||
runtimeVersion:
|
||||
(await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ??
|
||||
|
||||
@@ -66,6 +66,7 @@ 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';
|
||||
@@ -174,8 +175,9 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
}
|
||||
assert(projectArchive);
|
||||
|
||||
const runtimeMetadata = await createAndMaybeUploadFingerprintAsync(ctx);
|
||||
const metadata = await collectMetadataAsync(ctx, runtimeMetadata);
|
||||
const runtimeAndFingerprintMetadata =
|
||||
await computeAndMaybeUploadRuntimeAndFingerprintMetadataAsync(ctx);
|
||||
const metadata = await collectMetadataAsync(ctx, runtimeAndFingerprintMetadata);
|
||||
const buildParams = resolveBuildParamsInput(ctx, metadata);
|
||||
const job = await builder.prepareJobAsync(ctx, {
|
||||
projectArchive,
|
||||
@@ -660,11 +662,35 @@ function formatAccountBillingUrl(accountName: string): string {
|
||||
return new URL(`/accounts/${accountName}/settings/billing`, getExpoWebsiteBaseUrl()).toString();
|
||||
}
|
||||
|
||||
async function createAndMaybeUploadFingerprintAsync<T extends Platform>(
|
||||
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>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{
|
||||
runtimeVersion?: string;
|
||||
fingerprintSource?: FingerprintSource;
|
||||
fingerprintHash?: string;
|
||||
}> {
|
||||
const resolvedRuntimeVersion = await resolveRuntimeVersionAsync({
|
||||
exp: ctx.exp,
|
||||
@@ -689,10 +715,45 @@ async function createAndMaybeUploadFingerprintAsync<T extends Platform>(
|
||||
};
|
||||
}
|
||||
|
||||
return await maybeUploadFingerprintAsync({
|
||||
runtimeVersion: resolvedRuntimeVersion.runtimeVersion,
|
||||
const uploadedFingerprint = await maybeUploadFingerprintAsync({
|
||||
hash: 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,9 +54,10 @@ async function resolveEnvVarsAsync({
|
||||
projectId: string;
|
||||
}): Promise<Env> {
|
||||
const environment =
|
||||
buildProfile.environment?.toUpperCase() ?? process.env.EAS_CURRENT_ENVIRONMENT;
|
||||
buildProfile.environment?.toUpperCase() ??
|
||||
resolveSuggestedEnvironmentForBuildProfileConfiguration(buildProfile);
|
||||
|
||||
if (!environment || !isEnvironment(environment)) {
|
||||
if (!isEnvironment(environment)) {
|
||||
Log.log(
|
||||
`Loaded "env" configuration for the "${buildProfileName}" profile: ${
|
||||
buildProfile.env && Object.keys(buildProfile.env).length > 0
|
||||
@@ -81,21 +82,44 @@ async function resolveEnvVarsAsync({
|
||||
.map(({ name, value }) => [name, value])
|
||||
) as Record<string, string>;
|
||||
|
||||
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/')}`
|
||||
);
|
||||
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();
|
||||
|
||||
return { ...serverEnvVars, ...buildProfile.env };
|
||||
} catch (e) {
|
||||
@@ -107,3 +131,27 @@ async function resolveEnvVarsAsync({
|
||||
return { ...buildProfile.env };
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSuggestedEnvironmentForBuildProfileConfiguration(
|
||||
buildProfile: BuildProfile
|
||||
): EnvironmentVariableEnvironment {
|
||||
const setEnvironmentMessage =
|
||||
'Set the "environment" field in the build profile if you want to specify the environment manually.';
|
||||
if (buildProfile.distribution === 'store') {
|
||||
Log.log(
|
||||
`We detected that you are building for the "store" distribution. Resolving the environment for environment variables used during the build to "production". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Production;
|
||||
} else {
|
||||
if (buildProfile.developmentClient) {
|
||||
Log.log(
|
||||
`We detected that you are building the development client. Resolving the environment for environment variables used during the build to "development". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Development;
|
||||
}
|
||||
Log.log(
|
||||
`We detected that you are building for the "internal" distribution. Resolving the environment for environment variables used during the build to "preview". ${setEnvironmentMessage}`
|
||||
);
|
||||
return EnvironmentVariableEnvironment.Preview;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
import type { XCBuildConfiguration } from 'xcode';
|
||||
|
||||
import { learnMore } from '../../../log';
|
||||
import { readPlistAsync } from '../../../utils/plist';
|
||||
import { resolveVcsClient } from '../../../vcs';
|
||||
import {
|
||||
@@ -301,27 +300,6 @@ describe(readShortVersionAsync, () => {
|
||||
);
|
||||
expect(buildNumber).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('fails when build number is invalid', async () => {
|
||||
const exp = initBareWorkflowProject({
|
||||
appVersion: '$(CURRENT_PROJECT_VERSION)',
|
||||
});
|
||||
|
||||
await expect(
|
||||
readShortVersionAsync(
|
||||
'/app',
|
||||
exp,
|
||||
{
|
||||
CURRENT_PROJECT_VERSION: '0.0.7.1.028',
|
||||
},
|
||||
vcsClient
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: 0.0.7.1.028. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('managed project', () => {
|
||||
|
||||
@@ -110,6 +110,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@ import uniqBy from '../../utils/expodash/uniqBy';
|
||||
import { readPlistAsync, writePlistAsync } from '../../utils/plist';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
|
||||
import {
|
||||
bumpAppVersionAsync,
|
||||
ensureStaticConfigExists,
|
||||
getVersionConfigTarget,
|
||||
} from '../utils/version';
|
||||
|
||||
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
|
||||
|
||||
@@ -48,9 +52,11 @@ export async function bumpVersionAsync({
|
||||
ensureStaticConfigExists(projectDir);
|
||||
await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp });
|
||||
Log.log('Updated versions in app.json');
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
|
||||
const version = versionGetter(exp);
|
||||
await updateNativeVersionsAsync({
|
||||
projectDir,
|
||||
version: exp.version,
|
||||
version,
|
||||
buildNumber: exp.ios?.buildNumber,
|
||||
targets,
|
||||
});
|
||||
@@ -73,7 +79,7 @@ export async function bumpVersionInAppJsonAsync({
|
||||
Log.addNewLineIfNone();
|
||||
if (bumpStrategy === BumpStrategy.APP_VERSION) {
|
||||
const appVersion = IOSConfig.Version.getVersion(exp);
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp });
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
|
||||
} else {
|
||||
const buildNumber = IOSConfig.Version.getBuildNumber(exp);
|
||||
if (isValidBuildNumber(buildNumber)) {
|
||||
@@ -109,14 +115,14 @@ function validateShortVersion({
|
||||
}): void {
|
||||
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
|
||||
if (workflow === Workflow.MANAGED) {
|
||||
throw new Error(
|
||||
`The required format for "version" field from app.json/app.config.ts is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the required format. ${learnMore(
|
||||
Log.warn(
|
||||
`The required format for "version" field from app.json/app.config.ts for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
|
||||
Log.warn(
|
||||
`The required format for "CFBundleShortVersionString" in Info.plist for App Store iOS builds is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the format required by Apple. ${learnMore(
|
||||
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
|
||||
)}`
|
||||
);
|
||||
@@ -141,8 +147,10 @@ export async function readShortVersionAsync(
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
} else {
|
||||
validateShortVersion({ shortVersion: exp.version, workflow });
|
||||
return exp.version;
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS });
|
||||
const shortVersion = versionGetter(exp);
|
||||
validateShortVersion({ shortVersion, workflow });
|
||||
return shortVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.135';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.152';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,9 @@ import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>,
|
||||
runtimeMetadata: {
|
||||
runtimeAndFingerprintMetadata: {
|
||||
runtimeVersion?: string | undefined;
|
||||
fingerprintHash?: string | undefined;
|
||||
fingerprintSource?: FingerprintSource | undefined;
|
||||
}
|
||||
): Promise<Metadata> {
|
||||
@@ -30,8 +31,9 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion: runtimeMetadata?.runtimeVersion,
|
||||
fingerprintSource: runtimeMetadata?.fingerprintSource,
|
||||
runtimeVersion: runtimeAndFingerprintMetadata?.runtimeVersion,
|
||||
fingerprintHash: runtimeAndFingerprintMetadata?.fingerprintHash,
|
||||
fingerprintSource: runtimeAndFingerprintMetadata?.fingerprintSource,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
||||
...channelObject,
|
||||
distribution,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
AppVersionSource,
|
||||
@@ -381,7 +381,7 @@ async function prepareAndStartBuildAsync({
|
||||
vcsClient,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel: flags.buildLoggerLevel,
|
||||
buildLoggerLevel: flags.buildLoggerLevel ?? (Log.isDebug ? LoggerLevel.DEBUG : undefined),
|
||||
freezeCredentials: flags.freezeCredentials,
|
||||
repack: flags.repack,
|
||||
env,
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import { updateAppJsonConfigAsync } from '../appJson';
|
||||
import { bumpAppVersionAsync, getVersionConfigTarget } from '../version';
|
||||
|
||||
jest.mock('../appJson', () => ({
|
||||
__esModule: true,
|
||||
updateAppJsonConfigAsync: jest.fn().mockImplementation(
|
||||
async (
|
||||
{
|
||||
exp,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
},
|
||||
modifyConfig: (config: any) => void
|
||||
) => {
|
||||
// a mocked implementation that only mutates the config object without writing to disk
|
||||
modifyConfig(exp);
|
||||
}
|
||||
),
|
||||
}));
|
||||
|
||||
describe(bumpAppVersionAsync, () => {
|
||||
const name = 'test';
|
||||
const slug = 'test';
|
||||
const projectDir = '/app';
|
||||
const mockUpdateAppJsonConfigAsync = updateAppJsonConfigAsync as jest.MockedFunction<
|
||||
typeof updateAppJsonConfigAsync
|
||||
>;
|
||||
|
||||
it('should bump expo.version for valid semver', async () => {
|
||||
const appVersion = '1.0.0';
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: appVersion,
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('1.0.1');
|
||||
});
|
||||
|
||||
it('should bump expo.android.version if the expo.android.version exists', async () => {
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion: '1.0.0', projectDir, exp, platform: Platform.ANDROID });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('0.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.android.version).toBe('1.0.1');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.ios.version).toBe('2.0.0');
|
||||
});
|
||||
|
||||
it('should bump expo.ios.version if the expo.ios.version exists', async () => {
|
||||
const exp: ExpoConfig = {
|
||||
name,
|
||||
slug,
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
await bumpAppVersionAsync({ appVersion: '2.0.0', projectDir, exp, platform: Platform.IOS });
|
||||
expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled();
|
||||
expect(exp.version).toBe('0.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.android.version).toBe('1.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(exp.ios.version).toBe('2.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe(getVersionConfigTarget, () => {
|
||||
const exp: ExpoConfig = {
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
version: '0.0.0',
|
||||
android: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '1.0.0',
|
||||
},
|
||||
ios: {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
version: '2.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
it('should return the correct config target for the android platform', () => {
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.ANDROID,
|
||||
});
|
||||
expect(fieldName).toBe('expo.android.version');
|
||||
expect(versionGetter(exp)).toBe('1.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(versionUpdater(exp, '3.3.3').android.version).toBe('3.3.3');
|
||||
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
|
||||
});
|
||||
|
||||
it('should return the correct config target for the ios platform', () => {
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.IOS,
|
||||
});
|
||||
expect(fieldName).toBe('expo.ios.version');
|
||||
expect(versionGetter(exp)).toBe('2.0.0');
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
expect(versionUpdater(exp, '3.3.3').ios.version).toBe('3.3.3');
|
||||
expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0');
|
||||
});
|
||||
|
||||
it('should return the correct config target for common version', () => {
|
||||
const exp: ExpoConfig = {
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
version: '0.0.0',
|
||||
};
|
||||
const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({
|
||||
exp,
|
||||
platform: Platform.IOS,
|
||||
});
|
||||
expect(fieldName).toBe('expo.version');
|
||||
expect(versionGetter(exp)).toBe('0.0.0');
|
||||
expect(versionUpdater(exp, '3.3.3').version).toBe('3.3.3');
|
||||
});
|
||||
});
|
||||
@@ -50,3 +50,25 @@ 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();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
@@ -18,21 +19,25 @@ export async function bumpAppVersionAsync({
|
||||
appVersion,
|
||||
projectDir,
|
||||
exp,
|
||||
platform,
|
||||
}: {
|
||||
appVersion: string;
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
platform: Platform;
|
||||
}): Promise<void> {
|
||||
const { fieldName, versionUpdater } = getVersionConfigTarget({ exp, platform });
|
||||
|
||||
let bumpedAppVersion: string;
|
||||
if (semver.valid(appVersion)) {
|
||||
bumpedAppVersion = nullthrows(semver.inc(appVersion, 'patch'));
|
||||
Log.log(
|
||||
`Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold(
|
||||
`Bumping ${chalk.bold(fieldName)} from ${chalk.bold(appVersion)} to ${chalk.bold(
|
||||
bumpedAppVersion
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`);
|
||||
Log.log(`${chalk.bold(fieldName)} = ${chalk.bold(appVersion)} is not a valid semver`);
|
||||
bumpedAppVersion = (
|
||||
await promptAsync({
|
||||
type: 'text',
|
||||
@@ -42,6 +47,56 @@ export async function bumpAppVersionAsync({
|
||||
).bumpedAppVersion;
|
||||
}
|
||||
await updateAppJsonConfigAsync({ projectDir, exp }, config => {
|
||||
config.version = bumpedAppVersion;
|
||||
versionUpdater(config, bumpedAppVersion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target version field from ExpoConfig based on the platform.
|
||||
*/
|
||||
export function getVersionConfigTarget({
|
||||
exp,
|
||||
platform,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
platform: Platform;
|
||||
}): {
|
||||
fieldName: string;
|
||||
versionGetter: (config: ExpoConfig) => string | undefined;
|
||||
versionUpdater: (config: ExpoConfig, version: string) => ExpoConfig;
|
||||
} {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
if (platform === Platform.ANDROID && typeof exp.android?.version === 'string') {
|
||||
return {
|
||||
fieldName: 'expo.android.version',
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
versionGetter: config => config.android?.version,
|
||||
versionUpdater: (config, version) => {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
config.android = { ...config.android, version };
|
||||
return config;
|
||||
},
|
||||
};
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
} else if (platform === Platform.IOS && typeof exp.ios?.version === 'string') {
|
||||
return {
|
||||
fieldName: 'expo.ios.version',
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
versionGetter: config => config.ios?.version,
|
||||
versionUpdater: (config, version) => {
|
||||
// @ts-expect-error: Resolve type errors after upgrading `@expo/config`
|
||||
config.ios = { ...config.ios, version };
|
||||
return config;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fieldName: 'expo.version',
|
||||
versionGetter: config => config.version,
|
||||
versionUpdater: (config, version) => {
|
||||
config.version = version;
|
||||
return config;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -27,24 +27,28 @@ export async function checkGoogleServicesFileAsync<T extends Platform>(
|
||||
if (!googleServicesFilePath) {
|
||||
return;
|
||||
}
|
||||
const googleServicesEnvVar =
|
||||
ctx.platform === Platform.ANDROID
|
||||
? ctx.env.GOOGLE_SERVICES_JSON
|
||||
: ctx.env.GOOGLE_SERVICES_INFO_PLIST;
|
||||
const rootDir = path.normalize(await ctx.vcsClient.getRootPathAsync());
|
||||
const absGoogleServicesFilePath = path.resolve(ctx.projectDir, googleServicesFilePath);
|
||||
if (
|
||||
(await fs.pathExists(absGoogleServicesFilePath)) &&
|
||||
(!isInsideDirectory(absGoogleServicesFilePath, rootDir) ||
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(path.relative(rootDir, absGoogleServicesFilePath))))
|
||||
(await ctx.vcsClient.isFileIgnoredAsync(
|
||||
path.relative(rootDir, absGoogleServicesFilePath)
|
||||
))) &&
|
||||
!googleServicesEnvVar
|
||||
) {
|
||||
Log.warn(
|
||||
`File specified via "${ctx.platform}.googleServicesFile" field in your app.json is not checked in to your repository and won't be uploaded to the builder.`
|
||||
);
|
||||
Log.warn(
|
||||
`Use EAS Secret to pass all values that you don't want to include in your version control. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/variables/#using-secrets-in-environment-variables'
|
||||
`Use EAS file environment variables with secret or sensitive visibility to pass all values that you don't want to include in your version control to build process. ${learnMore(
|
||||
'https://docs.expo.dev/eas/environment-variables/#file-environment-variables'
|
||||
)}`
|
||||
);
|
||||
Log.warn(
|
||||
`If you are using that file for compatibility with the classic build service (expo build) you can silence this warning by setting your build profile's env.GOOGLE_SERVICES_FILE in eas.json to any non-empty string.`
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ 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',
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { BranchQuery, UpdateBranchOnChannelObject } from '../graphql/queries/BranchQuery';
|
||||
import { ChannelQuery, UpdateChannelObject } from '../graphql/queries/ChannelQuery';
|
||||
import { UpdateChannelBasicInfoFragmentNode } from '../graphql/types/UpdateChannelBasicInfo';
|
||||
import Log from '../log';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../project/projectUtils';
|
||||
import formatFields from '../utils/formatFields';
|
||||
import { printJsonOnlyOutput } from '../utils/json';
|
||||
@@ -110,7 +110,11 @@ export async function listAndRenderBranchesAndUpdatesOnChannelAsync(
|
||||
}
|
||||
): Promise<void> {
|
||||
const channel = await ChannelQuery.viewUpdateChannelAsync(graphqlClient, { appId, channelName });
|
||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
||||
renderChannelHeaderContent({
|
||||
channelName: channel.name,
|
||||
channelId: channel.id,
|
||||
isPaused: channel.isPaused,
|
||||
});
|
||||
|
||||
if (paginatedQueryOptions.nonInteractive) {
|
||||
const branches = await queryBranchesAndUpdateGroupsOnChannelAsync(graphqlClient, {
|
||||
@@ -167,7 +171,11 @@ function renderPageOfChannels(
|
||||
printJsonOnlyOutput({ currentPage });
|
||||
} else {
|
||||
for (const channel of currentPage) {
|
||||
renderChannelHeaderContent({ channelName: channel.name, channelId: channel.id });
|
||||
renderChannelHeaderContent({
|
||||
channelName: channel.name,
|
||||
channelId: channel.id,
|
||||
isPaused: channel.isPaused,
|
||||
});
|
||||
Log.addNewLineIfNone();
|
||||
logChannelDetails(channel);
|
||||
|
||||
@@ -196,9 +204,11 @@ function renderPageOfBranchesOnChannel(
|
||||
function renderChannelHeaderContent({
|
||||
channelName,
|
||||
channelId,
|
||||
isPaused,
|
||||
}: {
|
||||
channelName: string;
|
||||
channelId: string;
|
||||
isPaused: boolean;
|
||||
}): void {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Channel:'));
|
||||
@@ -206,6 +216,7 @@ function renderChannelHeaderContent({
|
||||
formatFields([
|
||||
{ label: 'Name', value: channelName },
|
||||
{ label: 'ID', value: channelId },
|
||||
{ label: 'Status', value: isPaused ? 'Paused' : 'Active' },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
@@ -292,7 +303,6 @@ export async function doesChannelExistAsync(
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a channel and links it to a branch with the same name.
|
||||
*
|
||||
* @param appId the app ID, also known as the project ID
|
||||
@@ -308,7 +318,7 @@ export async function createAndLinkChannelAsync(
|
||||
}: { appId: string; channelName: string; shouldPrintJson?: boolean }
|
||||
): Promise<ChannelBasicInfo> {
|
||||
let branchId: string;
|
||||
let branchMessage: string;
|
||||
let hasCreatedBranch = false;
|
||||
|
||||
try {
|
||||
const branch = await BranchQuery.getBranchByNameAsync(graphqlClient, {
|
||||
@@ -316,7 +326,6 @@ export async function createAndLinkChannelAsync(
|
||||
name: channelName,
|
||||
});
|
||||
branchId = branch.id;
|
||||
branchMessage = `We found a branch with the same name`;
|
||||
} catch (error) {
|
||||
if (error instanceof BranchNotFoundError) {
|
||||
const newBranch = await createUpdateBranchOnAppAsync(graphqlClient, {
|
||||
@@ -324,7 +333,7 @@ export async function createAndLinkChannelAsync(
|
||||
name: channelName,
|
||||
});
|
||||
branchId = newBranch.id;
|
||||
branchMessage = `We also went ahead and made a branch with the same name`;
|
||||
hasCreatedBranch = true;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@@ -347,26 +356,25 @@ export async function createAndLinkChannelAsync(
|
||||
if (shouldPrintJson) {
|
||||
printJsonOnlyOutput(newChannel);
|
||||
} else {
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, appId);
|
||||
const learnMoreLink = learnMore('https://docs.expo.dev/eas-update/eas-cli/');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(
|
||||
`Created a new channel on project ${chalk.bold(
|
||||
await getDisplayNameForProjectIdAsync(graphqlClient, appId)
|
||||
)}`
|
||||
);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: newChannel.id },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: branchId },
|
||||
])
|
||||
);
|
||||
if (hasCreatedBranch) {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} and branch ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} on ${chalk.bold(projectDisplayName)} project. ${learnMoreLink}`
|
||||
);
|
||||
} else {
|
||||
Log.withTick(
|
||||
`Created update channel ${chalk.bold(`"${newChannel.name}"`)} on ${chalk.bold(
|
||||
projectDisplayName
|
||||
)} project and connected it with existing ${chalk.bold(
|
||||
`"${newChannel.name}"`
|
||||
)} branch. ${learnMoreLink}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return newChannel;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import AnalyticsContextField from './context/AnalyticsContextField';
|
||||
import ContextField from './context/ContextField';
|
||||
import DynamicLoggedInContextField from './context/DynamicLoggedInContextField';
|
||||
import {
|
||||
DynamicPrivateProjectConfigContextField,
|
||||
DynamicPublicProjectConfigContextField,
|
||||
@@ -15,6 +16,8 @@ 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';
|
||||
@@ -23,6 +26,7 @@ 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';
|
||||
@@ -43,8 +47,26 @@ 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 = {
|
||||
/**
|
||||
@@ -61,8 +83,16 @@ 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(),
|
||||
@@ -72,7 +102,7 @@ export default abstract class EasCommand extends Command {
|
||||
* run within a project directory, null otherwise.
|
||||
*/
|
||||
OptionalProjectConfig: {
|
||||
privateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
||||
optionalPrivateProjectConfig: new OptionalPrivateProjectConfigContextField(),
|
||||
},
|
||||
/**
|
||||
* Require this command to be run in a project directory. Return the project directory in the context.
|
||||
@@ -107,6 +137,16 @@ 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(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -141,7 +181,19 @@ export default abstract class EasCommand extends Command {
|
||||
} = object,
|
||||
>(
|
||||
commandClass: { contextDefinition: ContextInput<C> },
|
||||
{ nonInteractive, vcsClientOverride }: { nonInteractive: boolean; vcsClientOverride?: Client }
|
||||
{
|
||||
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
|
||||
): Promise<ContextOutput<C>> {
|
||||
const contextDefinition = commandClass.contextDefinition;
|
||||
|
||||
@@ -155,6 +207,7 @@ export default abstract class EasCommand extends Command {
|
||||
sessionManager: this.sessionManager,
|
||||
analytics: this.analytics,
|
||||
vcsClientOverride,
|
||||
withServerSideEnvironment,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,12 @@ export async function fetchBuildsAsync({
|
||||
}: {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
projectId: string;
|
||||
filters?: { statuses?: BuildStatus[]; platform?: RequestedPlatform; profile?: string };
|
||||
filters?: {
|
||||
statuses?: BuildStatus[];
|
||||
platform?: RequestedPlatform;
|
||||
profile?: string;
|
||||
hasFingerprint?: boolean;
|
||||
};
|
||||
}): Promise<BuildFragment[]> {
|
||||
let builds: BuildFragment[];
|
||||
const queryFilters: InputMaybe<BuildFilter> = {};
|
||||
@@ -48,6 +53,9 @@ export async function fetchBuildsAsync({
|
||||
if (filters?.profile) {
|
||||
queryFilters['buildProfile'] = filters.profile;
|
||||
}
|
||||
if (filters?.hasFingerprint) {
|
||||
queryFilters['hasFingerprint'] = filters.hasFingerprint;
|
||||
}
|
||||
if (!filters?.statuses) {
|
||||
builds = await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Analytics } from '../../analytics/AnalyticsManager';
|
||||
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
|
||||
import SessionManager from '../../user/SessionManager';
|
||||
import { Client } from '../../vcs/vcs';
|
||||
|
||||
@@ -7,6 +8,10 @@ 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> {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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,12 +1,14 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
|
||||
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,
|
||||
getPrivateExpoConfig,
|
||||
getPublicExpoConfig,
|
||||
getPrivateExpoConfigAsync,
|
||||
getPublicExpoConfigAsync,
|
||||
} from '../../project/expoConfig';
|
||||
|
||||
export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{
|
||||
@@ -19,15 +21,34 @@ export class DynamicPublicProjectConfigContextField extends ContextField<Dynamic
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
const expBefore = getPublicExpoConfig(projectDir, options);
|
||||
const expBefore = await getPublicExpoConfigAsync(projectDir, options);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
env: options?.env,
|
||||
});
|
||||
const exp = getPublicExpoConfig(projectDir, options);
|
||||
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);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
@@ -41,15 +62,34 @@ export class DynamicPrivateProjectConfigContextField extends ContextField<Dynami
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<DynamicConfigContextFn> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
const expBefore = getPrivateExpoConfig(projectDir, options);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir, options);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
env: options?.env,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
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);
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
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 default class LoggedInContextField extends ContextField<{
|
||||
type LoggedInContextType = {
|
||||
actor: Actor;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
||||
actor: Actor;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
authenticationInfo: LoggedInAuthenticationInfo;
|
||||
};
|
||||
|
||||
export default class LoggedInContextField extends ContextField<LoggedInContextType> {
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
}: ContextOptions): Promise<LoggedInContextType> {
|
||||
const { actor, authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
@@ -25,6 +28,7 @@ export default class LoggedInContextField extends ContextField<{
|
||||
actor,
|
||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
authenticationInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,18 @@ import { Actor } from '../../user/User';
|
||||
import FeatureGateEnvOverrides from '../gating/FeatureGateEnvOverrides';
|
||||
import FeatureGating from '../gating/FeatureGating';
|
||||
|
||||
export default class MaybeLoggedInContextField extends ContextField<{
|
||||
type MaybeLoggedInContextType = {
|
||||
actor: Actor | null;
|
||||
featureGating: FeatureGating;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
}> {
|
||||
async getValueAsync({ sessionManager }: ContextOptions): Promise<{
|
||||
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> {
|
||||
const authenticationInfo = {
|
||||
accessToken: sessionManager.getAccessToken(),
|
||||
sessionSecret: sessionManager.getSessionSecret(),
|
||||
@@ -27,6 +29,7 @@ export default class MaybeLoggedInContextField extends ContextField<{
|
||||
actor: actor ?? null,
|
||||
featureGating: new FeatureGating(featureGateServerValues, new FeatureGateEnvOverrides()),
|
||||
graphqlClient,
|
||||
authenticationInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
+24
-5
@@ -1,10 +1,12 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
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 { getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
| {
|
||||
@@ -14,7 +16,11 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<
|
||||
| {
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
@@ -35,11 +41,24 @@ export class OptionalPrivateProjectConfigContextField extends ContextField<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const expBefore = getPrivateExpoConfig(projectDir);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir);
|
||||
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 });
|
||||
return {
|
||||
exp,
|
||||
projectDir,
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
|
||||
import ContextField, { ContextOptions } from './ContextField';
|
||||
import { createGraphqlClient } from './contextUtils/createGraphqlClient';
|
||||
import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { getProjectIdAsync } from './contextUtils/getProjectIdAsync';
|
||||
import { getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync';
|
||||
import { getPrivateExpoConfigAsync } from '../../project/expoConfig';
|
||||
|
||||
export class PrivateProjectConfigContextField extends ContextField<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise<{
|
||||
async getValueAsync({
|
||||
nonInteractive,
|
||||
sessionManager,
|
||||
withServerSideEnvironment,
|
||||
}: ContextOptions): Promise<{
|
||||
projectId: string;
|
||||
exp: ExpoConfig;
|
||||
projectDir: string;
|
||||
}> {
|
||||
const projectDir = await findProjectDirAndVerifyProjectSetupAsync();
|
||||
const expBefore = getPrivateExpoConfig(projectDir);
|
||||
const expBefore = await getPrivateExpoConfigAsync(projectDir);
|
||||
const projectId = await getProjectIdAsync(sessionManager, expBefore, {
|
||||
nonInteractive,
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir);
|
||||
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 });
|
||||
|
||||
return {
|
||||
projectId,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -21,6 +22,7 @@ jest.mock('../../../../ora', () => ({
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
|
||||
describe(getProjectIdAsync, () => {
|
||||
let sessionManager: SessionManager;
|
||||
@@ -71,6 +73,7 @@ describe(getProjectIdAsync, () => {
|
||||
);
|
||||
|
||||
jest.mocked(findProjectRootAsync).mockResolvedValue('/app');
|
||||
jest.mocked(isExpoInstalled).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('gets the project ID from app config if exists', async () => {
|
||||
@@ -222,7 +225,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(getConfig).mockReturnValue({ exp: { name: 'test', slug: 'test' } } as any);
|
||||
jest.mocked(modifyConfigAsync).mockResolvedValue({
|
||||
type: 'success',
|
||||
config: { expo: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } } },
|
||||
config: { name: 'test', slug: 'test', extra: { eas: { projectId: '2345' } } },
|
||||
});
|
||||
jest
|
||||
.mocked(fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ async function ensureEasCliIsNotInDependenciesAsync(projectDir: string): Promise
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your project dependencies.`);
|
||||
}
|
||||
|
||||
const maybeRepoRoot = PackageManagerUtils.findWorkspaceRoot(projectDir) ?? projectDir;
|
||||
const maybeRepoRoot = PackageManagerUtils.resolveWorkspaceRoot(projectDir) ?? projectDir;
|
||||
if (maybeRepoRoot !== projectDir && (await isEasCliInDependenciesAsync(maybeRepoRoot))) {
|
||||
printCliVersionWarning = true;
|
||||
consoleWarn(`Found ${chalk.bold('eas-cli')} in your monorepo dependencies.`);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getProjectConfigDescription } from '@expo/config';
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { ExpoConfig, getProjectConfigDescription } from '@expo/config';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
@@ -8,9 +7,11 @@ import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig';
|
||||
import {
|
||||
createOrModifyExpoConfigAsync,
|
||||
getPrivateExpoConfigAsync,
|
||||
} from '../../../project/expoConfig';
|
||||
import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync';
|
||||
import { toAppPrivacy } from '../../../project/projectUtils';
|
||||
import SessionManager from '../../../user/SessionManager';
|
||||
import { Actor, getActorUsername } from '../../../user/User';
|
||||
|
||||
@@ -26,7 +27,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 = getPrivateExpoConfig(projectDir, { skipPlugins: true, ...options });
|
||||
const exp = await getPrivateExpoConfigAsync(projectDir, { skipPlugins: true, ...options });
|
||||
const result = await createOrModifyExpoConfigAsync(
|
||||
projectDir,
|
||||
{
|
||||
@@ -195,7 +196,6 @@ export async function validateOrSetProjectIdAsync({
|
||||
{
|
||||
accountName: getAccountNameForEASProjectSync(exp, actor),
|
||||
projectName: exp.slug,
|
||||
privacy: toAppPrivacy(exp.privacy),
|
||||
},
|
||||
{
|
||||
nonInteractive: options.nonInteractive,
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../graphql/generated';
|
||||
import { EnvironmentVariableEnvironment } from '../graphql/generated';
|
||||
|
||||
// NOTE: not exactly true, but, provided mapToLowercase and upperCaseAsync
|
||||
// are used in tandem, it saves on unnecessary typying in commands
|
||||
@@ -56,23 +52,19 @@ export const EASVariableFormatFlag = {
|
||||
};
|
||||
|
||||
export const EASVariableVisibilityFlag = {
|
||||
visibility: Flags.enum<EnvironmentVariableVisibility>({
|
||||
visibility: Flags.enum<'plaintext' | 'sensitive' | 'secret'>({
|
||||
description: 'Visibility of the variable',
|
||||
options: mapToLowercase([
|
||||
EnvironmentVariableVisibility.Secret,
|
||||
EnvironmentVariableVisibility.Sensitive,
|
||||
EnvironmentVariableVisibility.Public,
|
||||
]),
|
||||
parse: upperCaseAsync,
|
||||
options: ['plaintext', 'sensitive', 'secret'],
|
||||
}),
|
||||
};
|
||||
|
||||
export const EASVariableScopeFlag = {
|
||||
scope: Flags.enum<EnvironmentVariableScope>({
|
||||
export type EASEnvironmentVariableScopeFlagValue = 'project' | 'account';
|
||||
|
||||
export const EASEnvironmentVariableScopeFlag = {
|
||||
scope: Flags.enum<EASEnvironmentVariableScopeFlagValue>({
|
||||
description: 'Scope for the variable',
|
||||
options: mapToLowercase([EnvironmentVariableScope.Shared, EnvironmentVariableScope.Project]),
|
||||
parse: upperCaseAsync,
|
||||
default: EnvironmentVariableScope.Project,
|
||||
options: ['project', 'account'],
|
||||
default: 'project',
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -87,3 +79,14 @@ 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,16 +12,14 @@ export default class AccountView extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.MaybeLoggedIn,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
maybeLoggedIn: { actor },
|
||||
sessionManager,
|
||||
maybeLoggedIn: { actor, authenticationInfo },
|
||||
} = await this.getContextAsync(AccountView, { nonInteractive: true });
|
||||
if (actor) {
|
||||
const loggedInAs = sessionManager.getAccessToken()
|
||||
const loggedInAs = authenticationInfo.accessToken
|
||||
? `${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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
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),
|
||||
initial: (await getDefaultBranchNameAsync(vcsClient)) ?? undefined,
|
||||
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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class BranchDelete extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchDelete, { nonInteractive });
|
||||
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
@@ -13,14 +13,14 @@ export default class BranchList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(BranchList);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchRename, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class BranchView extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class BranchView extends EasCommand {
|
||||
} = await this.parse(BranchView);
|
||||
const { 'non-interactive': nonInteractive } = flags;
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BranchView, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class BuildCancel extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -128,7 +128,7 @@ export default class BuildCancel extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildCancel, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -38,6 +38,7 @@ 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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -112,7 +112,7 @@ export default class BuildDelete extends EasCommand {
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildDelete, { nonInteractive });
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
@@ -142,6 +142,7 @@ export default class Build extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(Build, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
@@ -79,6 +79,7 @@ export default class BuildInspect extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildInspect, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const outputDirectory = path.resolve(process.cwd(), flags.output);
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
@@ -63,7 +64,8 @@ export default class BuildInternal extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildInternal, {
|
||||
nonInteractive: true,
|
||||
vcsClientOverride: new GitNoCommitClient(),
|
||||
vcsClientOverride: process.env.EAS_NO_VCS ? new NoVcsClient() : new GitNoCommitClient(),
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
await handleDeprecatedEasJsonAsync(projectDir, flags.nonInteractive);
|
||||
|
||||
@@ -72,7 +72,7 @@ export default class BuildList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -99,7 +99,7 @@ export default class BuildList extends EasCommand {
|
||||
process.exit(1);
|
||||
}
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildList, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -120,6 +120,7 @@ export default class BuildResign extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildResign, {
|
||||
nonInteractive: flags.nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const maybeBuild = flags.maybeBuildId
|
||||
|
||||
@@ -79,8 +79,7 @@ export default class Run extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
|
||||
@@ -90,7 +89,7 @@ export default class Run extends EasCommand {
|
||||
const queryOptions = getPaginatedQueryOptions(flags);
|
||||
const {
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
} = await this.getContextAsync(Run, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionGetView, {
|
||||
nonInteractive: true,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
if (!flags.platform && flags['non-interactive']) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync';
|
||||
import { getVersionConfigTarget } from '../../../build/utils/version';
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation';
|
||||
import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery';
|
||||
@@ -54,6 +55,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionSetView, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const platform = await selectPlatformAsync(flags.platform);
|
||||
@@ -110,6 +112,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
: `What version would you like to initialize it with?`;
|
||||
Log.log(currentStateMessage);
|
||||
|
||||
const { versionGetter } = getVersionConfigTarget({ exp, platform });
|
||||
const { version } = await promptAsync({
|
||||
type: platform === Platform.ANDROID ? 'number' : 'text',
|
||||
name: 'version',
|
||||
@@ -123,7 +126,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
appId: projectId,
|
||||
platform: toAppPlatform(platform),
|
||||
applicationIdentifier,
|
||||
storeVersion: exp.version ?? '1.0.0',
|
||||
storeVersion: versionGetter(exp) ?? '1.0.0',
|
||||
buildVersion: String(version),
|
||||
runtimeVersion:
|
||||
(await getRuntimeVersionNullableAsync(projectDir, exp, platform)) ?? undefined,
|
||||
|
||||
@@ -73,6 +73,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(BuildVersionSyncView, {
|
||||
nonInteractive: true,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const requestedPlatform = await selectRequestedPlatformAsync(flags.platform);
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class BuildView extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export default class BuildView extends EasCommand {
|
||||
flags,
|
||||
} = await this.parse(BuildView);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(BuildView, {
|
||||
nonInteractive: true,
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export default class ChannelCreate extends EasCommand {
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ChannelCreate);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelCreate, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class ChannelDelete extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class ChannelDelete extends EasCommand {
|
||||
flags: { json: jsonFlag, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(ChannelDelete);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelDelete, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -74,7 +74,7 @@ export default class ChannelEdit extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelEdit, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class ChannelList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelList, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
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.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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,6 +138,7 @@ 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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelView, {
|
||||
nonInteractive,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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';
|
||||
@@ -35,7 +34,7 @@ export default class Config extends EasCommand {
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.SessionManagment,
|
||||
...this.ContextOptions.DynamicLoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
@@ -48,9 +47,10 @@ export default class Config extends EasCommand {
|
||||
profile: maybeProfile,
|
||||
'non-interactive': nonInteractive,
|
||||
} = flags;
|
||||
const { getDynamicPublicProjectConfigAsync, projectDir, sessionManager } =
|
||||
const { getDynamicPublicProjectConfigAsync, projectDir, getDynamicLoggedInAsync } =
|
||||
await this.getContextAsync(Config, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const accessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
@@ -88,10 +88,7 @@ export default class Config extends EasCommand {
|
||||
Log.log(JSON.stringify(profile, null, 2));
|
||||
}
|
||||
} else {
|
||||
const { authenticationInfo } = await sessionManager.ensureLoggedInAsync({
|
||||
nonInteractive,
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
const { graphqlClient } = await getDynamicLoggedInAsync();
|
||||
const { exp: appConfig } = await evaluateConfigWithEnvVarsAsync({
|
||||
buildProfile: profile,
|
||||
buildProfileName: profileName,
|
||||
|
||||
@@ -39,6 +39,7 @@ export default class InitializeBuildCredentials extends EasCommand {
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(InitializeBuildCredentials, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const platform = await selectPlatformAsync(flags.platform);
|
||||
|
||||
@@ -22,12 +22,13 @@ export default class Credentials extends EasCommand {
|
||||
const { flags } = await this.parse(Credentials);
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
privateProjectConfig,
|
||||
optionalPrivateProjectConfig: privateProjectConfig,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
analytics,
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(Credentials, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
await new SelectPlatform(
|
||||
|
||||
@@ -15,9 +15,10 @@ export default class DeviceCreate extends EasCommand {
|
||||
// this command is interactive by design
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
privateProjectConfig,
|
||||
optionalPrivateProjectConfig: 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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(DeviceDelete, {
|
||||
nonInteractive: paginatedQueryOptions.nonInteractive,
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class BuildList extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class BuildList extends EasCommand {
|
||||
const { flags } = await this.parse(BuildList);
|
||||
const paginatedQueryOptions = getPaginatedQueryOptions(flags);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(DeviceRename, {
|
||||
nonInteractive: paginatedQueryOptions.nonInteractive,
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class DeviceView extends EasCommand {
|
||||
static override args = [{ name: 'UDID' }];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(DeviceView, {
|
||||
nonInteractive: true,
|
||||
|
||||
+50
-25
@@ -13,19 +13,20 @@ 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 EnvironmentVariableCreate from '../create';
|
||||
import EnvCreate from '../create';
|
||||
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../utils/prompts');
|
||||
|
||||
describe(EnvironmentVariableCreate, () => {
|
||||
describe(EnvCreate, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const variableId = 'testId';
|
||||
@@ -33,6 +34,10 @@ describe(EnvironmentVariableCreate, () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest
|
||||
.mocked(parseVisibility)
|
||||
.mockImplementation(jest.requireActual('../../../utils/prompts').parseVisibility);
|
||||
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
jest
|
||||
.mocked(EnvironmentVariableMutation.createForAppAsync)
|
||||
@@ -69,15 +74,24 @@ describe(EnvironmentVariableCreate, () => {
|
||||
|
||||
describe('in interactive mode', () => {
|
||||
it('creates a project variable', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
['--name', 'VarName', '--value', 'VarValue', '--environment', 'production'],
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
'--value',
|
||||
'VarValue',
|
||||
'--environment',
|
||||
'production',
|
||||
'--visibility',
|
||||
'secret',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
@@ -88,7 +102,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
name: 'VarName',
|
||||
value: 'VarValue',
|
||||
environments: [EnvironmentVariableEnvironment.Production],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
visibility: EnvironmentVariableVisibility.Secret,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
testProjectId
|
||||
@@ -96,15 +110,24 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
|
||||
it('updates an existing variable in the same environment', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
['--name', 'VarName', '--value', 'VarValue', '--environment', 'production'],
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
'--value',
|
||||
'VarValue',
|
||||
'--environment',
|
||||
'production',
|
||||
'--visibility',
|
||||
'secret',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
const otherVariableId = 'otherId';
|
||||
@@ -144,12 +167,12 @@ describe(EnvironmentVariableCreate, () => {
|
||||
name: 'VarName',
|
||||
value: 'VarValue',
|
||||
environments: [EnvironmentVariableEnvironment.Production],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
visibility: EnvironmentVariableVisibility.Secret,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a shared variable', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('creates an account-wide variable', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -158,7 +181,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -176,6 +199,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
{
|
||||
name: 'VarName',
|
||||
value: 'VarValue',
|
||||
isGlobal: true,
|
||||
environments: [EnvironmentVariableEnvironment.Production],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
@@ -184,8 +208,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if a shared variable already exists', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('throws if an account-wide variable already exists', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -194,7 +218,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -219,8 +243,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
await expect(command.runAsync()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('updates if a shared variable already exists and --force flag is set', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('updates if an account-wide variable already exists and --force flag is set', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -230,7 +254,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'production',
|
||||
'--force',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -265,8 +289,8 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a shared variable and links it', async () => {
|
||||
const command = new EnvironmentVariableCreate(
|
||||
it('creates an account-wide variable and links it', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'--name',
|
||||
'VarName',
|
||||
@@ -277,7 +301,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
'--environment',
|
||||
'development',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
'--link',
|
||||
],
|
||||
mockConfig
|
||||
@@ -286,7 +310,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
@@ -301,6 +325,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
EnvironmentVariableEnvironment.Development,
|
||||
],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
isGlobal: true,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
testAccountId
|
||||
@@ -320,7 +345,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
});
|
||||
|
||||
it('prompts for missing arguments', async () => {
|
||||
const command = new EnvironmentVariableCreate([], mockConfig);
|
||||
const command = new EnvCreate([], mockConfig);
|
||||
|
||||
jest.mocked(promptVariableNameAsync).mockImplementation(async () => 'VarName');
|
||||
jest.mocked(promptVariableValueAsync).mockImplementation(async () => 'VarValue');
|
||||
@@ -336,7 +361,7 @@ describe(EnvironmentVariableCreate, () => {
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
+7
-10
@@ -8,14 +8,14 @@ import { EnvironmentVariableMutation } from '../../../graphql/mutations/Environm
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableDelete from '../delete';
|
||||
import EnvDelete from '../delete';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableDelete, () => {
|
||||
describe(EnvDelete, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -40,7 +40,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(
|
||||
const command = new EnvDelete(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -66,7 +66,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableDelete([], mockConfig);
|
||||
const command = new EnvDelete([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -82,10 +82,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvDelete(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
@@ -99,7 +96,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
|
||||
const command = new EnvDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
@@ -116,7 +113,7 @@ describe(EnvironmentVariableDelete, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(false);
|
||||
|
||||
const command = new EnvironmentVariableDelete(['--non-interactive'], mockConfig);
|
||||
const command = new EnvDelete(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
+6
-6
@@ -12,14 +12,14 @@ import {
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../../utils/prompts';
|
||||
import EnvironmentVariableGet from '../get';
|
||||
import EnvGet from '../get';
|
||||
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../utils/prompts');
|
||||
|
||||
describe(EnvironmentVariableGet, () => {
|
||||
describe(EnvGet, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const mockVariables: EnvironmentVariableFragment[] = [
|
||||
@@ -42,12 +42,12 @@ describe(EnvironmentVariableGet, () => {
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
const command = new EnvironmentVariableGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
|
||||
const command = new EnvGet(['--variable-name', 'TEST_VAR_1'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
@@ -68,7 +68,7 @@ describe(EnvironmentVariableGet, () => {
|
||||
const errorMessage =
|
||||
"Variable name is required. Run the command with '--variable-name VARIABLE_NAME' flag";
|
||||
|
||||
const command = new EnvironmentVariableGet([], mockConfig);
|
||||
const command = new EnvGet([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
@@ -85,7 +85,7 @@ describe(EnvironmentVariableGet, () => {
|
||||
.mockResolvedValueOnce(EnvironmentVariableEnvironment.Production);
|
||||
jest.mocked(promptVariableNameAsync).mockResolvedValueOnce('TEST_VAR_1');
|
||||
|
||||
const command = new EnvironmentVariableGet([], mockConfig);
|
||||
const command = new EnvGet([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
+14
-20
@@ -11,7 +11,7 @@ import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, selectAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableLink from '../link';
|
||||
import EnvLink from '../link';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
@@ -19,13 +19,13 @@ jest.mock('../../../prompts');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableLink, () => {
|
||||
describe(EnvLink, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const mockContext = {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
});
|
||||
|
||||
it('links a shared variable to the current project in non-interactive mode', async () => {
|
||||
it('links an account-wide variable to the current project in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -53,7 +53,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
mockVariables[0]
|
||||
);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -75,7 +75,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('links a shared variable to the current project to a specified environment', async () => {
|
||||
it('links an account-wide variable to the current project to a specified environment', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -89,7 +89,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
mockVariables[0]
|
||||
);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -137,7 +137,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
);
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
const command = new EnvLink(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -186,7 +186,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ environments: mockVariables[0].environments });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink([], mockConfig);
|
||||
const command = new EnvLink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -207,15 +207,12 @@ describe(EnvironmentVariableLink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -239,7 +236,7 @@ describe(EnvironmentVariableLink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableLink([], mockConfig);
|
||||
const command = new EnvLink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -263,15 +260,12 @@ describe(EnvironmentVariableLink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableLink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvLink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
+14
-17
@@ -13,7 +13,7 @@ import {
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import EnvironmentVariableList from '../list';
|
||||
import EnvList from '../list';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
@@ -44,7 +44,7 @@ const mockVariables: EnvironmentVariableFragment[] = [
|
||||
},
|
||||
];
|
||||
|
||||
describe(EnvironmentVariableList, () => {
|
||||
describe(EnvList, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
|
||||
@@ -56,12 +56,12 @@ describe(EnvironmentVariableList, () => {
|
||||
it('lists project environment variables successfully', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList([], mockConfig);
|
||||
const command = new EnvList([], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
await command.runAsync();
|
||||
|
||||
@@ -77,12 +77,12 @@ describe(EnvironmentVariableList, () => {
|
||||
it('lists project environment variables in specified environments', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--environment', 'production'], mockConfig);
|
||||
const command = new EnvList(['--environment', 'production'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
await command.runAsync();
|
||||
|
||||
@@ -100,12 +100,12 @@ describe(EnvironmentVariableList, () => {
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--include-sensitive'], mockConfig);
|
||||
const command = new EnvList(['--include-sensitive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
await command.runAsync();
|
||||
|
||||
@@ -121,15 +121,15 @@ describe(EnvironmentVariableList, () => {
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
|
||||
});
|
||||
|
||||
it('lists shared environment variables successfully', async () => {
|
||||
it('lists account-wide environment variables successfully', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.sharedAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(['--scope', 'shared'], mockConfig);
|
||||
const command = new EnvList(['--scope', 'account'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
await command.runAsync();
|
||||
|
||||
@@ -142,20 +142,17 @@ describe(EnvironmentVariableList, () => {
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
|
||||
});
|
||||
|
||||
it('lists shared environment variables including sensitive values', async () => {
|
||||
it('lists account-wide environment variables including sensitive values', async () => {
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.sharedWithSensitiveAsync)
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableList(
|
||||
['--include-sensitive', '--scope', 'shared'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvList(['--include-sensitive', '--scope', 'account'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
privateProjectConfig: { projectId: testProjectId },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
await command.runAsync();
|
||||
|
||||
+13
-19
@@ -11,7 +11,7 @@ import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { promptAsync, selectAsync, toggleConfirmAsync } from '../../../prompts';
|
||||
import EnvironmentVariableUnlink from '../unlink';
|
||||
import EnvUnlink 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(EnvironmentVariableUnlink, () => {
|
||||
describe(EnvUnlink, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const mockContext = {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
|
||||
});
|
||||
|
||||
it('unlinks a shared variable from the current project in non-interactive mode', async () => {
|
||||
it('unlinks an account-wide variable from the current project in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -54,7 +54,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
|
||||
).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
const command = new EnvUnlink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -76,7 +76,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('unlinks a shared variable from the current project in a specified environment', async () => {
|
||||
it('unlinks an account-wide variable from the current project in a specified environment', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -91,7 +91,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
|
||||
).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
const command = new EnvUnlink(
|
||||
['--variable-name', 'TEST_VARIABLE', '--environment', 'production', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
@@ -141,7 +141,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUnlink([], mockConfig);
|
||||
const command = new EnvUnlink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -162,15 +162,12 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -194,7 +191,7 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
});
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUnlink([], mockConfig);
|
||||
const command = new EnvUnlink([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -218,15 +215,12 @@ describe(EnvironmentVariableUnlink, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUnlink(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUnlink(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
"Account-wide variable NON_EXISTENT_VARIABLE doesn't exist"
|
||||
);
|
||||
});
|
||||
});
|
||||
+12
-11
@@ -5,13 +5,14 @@ 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 EnvironmentVariableUpdate from '../update';
|
||||
import EnvUpdate from '../update';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
@@ -19,7 +20,7 @@ jest.mock('../../../prompts');
|
||||
jest.mock('../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvironmentVariableUpdate, () => {
|
||||
describe(EnvUpdate, () => {
|
||||
const projectId = 'test-project-id';
|
||||
const variableId = '1';
|
||||
const graphqlClient = {};
|
||||
@@ -46,7 +47,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -59,6 +60,8 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
'NEW_VARIABLE',
|
||||
'--environment',
|
||||
'production',
|
||||
'--visibility',
|
||||
'plaintext',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -71,13 +74,14 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
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 a shared variable by selected name in non-interactive mode', async () => {
|
||||
it('updates an account-wide variable by selected name in non-interactive mode', async () => {
|
||||
const mockVariables = [
|
||||
{
|
||||
id: variableId,
|
||||
@@ -89,7 +93,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
(EnvironmentVariableMutation.updateAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'--variable-name',
|
||||
'TEST_VARIABLE',
|
||||
@@ -103,7 +107,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
'--environment',
|
||||
'production',
|
||||
'--scope',
|
||||
'shared',
|
||||
'account',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
@@ -131,7 +135,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
(promptAsync as jest.Mock).mockResolvedValue({ name: mockVariables[0].name });
|
||||
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvironmentVariableUpdate([], mockConfig);
|
||||
const command = new EnvUpdate([], mockConfig);
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
await command.runAsync();
|
||||
@@ -150,10 +154,7 @@ describe(EnvironmentVariableUpdate, () => {
|
||||
const mockVariables: never[] = [];
|
||||
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
|
||||
|
||||
const command = new EnvironmentVariableUpdate(
|
||||
['--variable-name', 'NON_EXISTENT_VARIABLE'],
|
||||
mockConfig
|
||||
);
|
||||
const command = new EnvUpdate(['--variable-name', 'NON_EXISTENT_VARIABLE'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnvDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with [1m--variable-name VARIABLE_NAME[22m flag to fix the issue"`;
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnvironmentVariableDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with [1m--variable-name VARIABLE_NAME[22m flag to fix the issue"`;
|
||||
+111
-55
@@ -5,9 +5,10 @@ import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import {
|
||||
@@ -25,30 +26,50 @@ import {
|
||||
} from '../../project/projectUtils';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import {
|
||||
parseVisibility,
|
||||
promptVariableEnvironmentAsync,
|
||||
promptVariableNameAsync,
|
||||
promptVariableTypeAsync,
|
||||
promptVariableValueAsync,
|
||||
promptVariableVisibilityAsync,
|
||||
} from '../../utils/prompts';
|
||||
import { performForEnvironmentsAsync } from '../../utils/variableUtils';
|
||||
import { isEnvironment, performForEnvironmentsAsync } from '../../utils/variableUtils';
|
||||
|
||||
type CreateFlags = {
|
||||
interface RawCreateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
link?: boolean;
|
||||
force?: boolean;
|
||||
type?: 'string' | 'file';
|
||||
visibility?: EnvironmentVariableVisibility;
|
||||
scope?: EnvironmentVariableScope;
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableCreate extends EasCommand {
|
||||
static override description =
|
||||
'create an environment variable on the current project or owner account';
|
||||
interface CreateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
link?: boolean;
|
||||
force?: boolean;
|
||||
type?: 'string' | 'file';
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
scope: EnvironmentVariableScope;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvCreate extends EasCommand {
|
||||
static override description = 'create an environment variable for the current project or account';
|
||||
|
||||
static override args = [
|
||||
{
|
||||
name: 'environment',
|
||||
description:
|
||||
"Environment to create the variable in. One of 'production', 'preview', or 'development'.",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
name: Flags.string({
|
||||
@@ -58,7 +79,8 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
description: 'Text value or the variable',
|
||||
}),
|
||||
link: Flags.boolean({
|
||||
description: 'Link shared variable to the current project',
|
||||
description: 'Link account-wide variable to the current project',
|
||||
hidden: true, // every account-wide variable is global for now so it's not user facing
|
||||
}),
|
||||
force: Flags.boolean({
|
||||
description: 'Overwrite existing variable',
|
||||
@@ -69,21 +91,21 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
options: ['string', 'file'],
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableCreate);
|
||||
const { args, flags } = await this.parse(EnvCreate);
|
||||
|
||||
const validatedFlags = this.validateFlags(flags);
|
||||
const validatedFlags = this.sanitizeFlags(flags);
|
||||
|
||||
const {
|
||||
name,
|
||||
@@ -95,12 +117,13 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
link,
|
||||
force,
|
||||
type,
|
||||
} = await this.promptForMissingFlagsAsync(validatedFlags);
|
||||
fileName,
|
||||
} = await this.promptForMissingFlagsAsync(validatedFlags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableCreate, {
|
||||
} = await this.getContextAsync(EnvCreate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -135,12 +158,12 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
await this.promptForOverwriteAsync({
|
||||
nonInteractive,
|
||||
force,
|
||||
message: `Shared variable with ${name} name already exists on this account.`,
|
||||
message: `Account-wide variable with ${name} name already exists on this account.`,
|
||||
suggestion: 'Do you want to unlink it first?',
|
||||
});
|
||||
|
||||
Log.withTick(
|
||||
`Unlinking shared variable ${chalk.bold(name)} on project ${chalk.bold(
|
||||
`Unlinking account-wide variable ${chalk.bold(name)} on project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
@@ -165,6 +188,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
visibility,
|
||||
environments,
|
||||
type,
|
||||
fileName,
|
||||
})
|
||||
: await EnvironmentVariableMutation.createForAppAsync(
|
||||
graphqlClient,
|
||||
@@ -174,6 +198,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
environments,
|
||||
visibility,
|
||||
type: type ?? EnvironmentSecretType.String,
|
||||
fileName,
|
||||
},
|
||||
projectId
|
||||
);
|
||||
@@ -202,8 +227,8 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
overwrite = true;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Shared variable with ${name} name already exists on this account.\n` +
|
||||
`Use a different name or delete the existing variable on website or by using eas env:delete --name ${name} --scope shared command.`
|
||||
`Account-wide variable with ${name} name already exists on this account.\n` +
|
||||
`Use a different name or delete the existing variable on website or by using the "eas env:delete --name ${name} --scope account" command.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -225,6 +250,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
value,
|
||||
visibility,
|
||||
environments,
|
||||
isGlobal: true, // TODO: every account-wide variable is global for now so it's not user facing
|
||||
type: type ?? EnvironmentSecretType.String,
|
||||
},
|
||||
ownerAccount.id
|
||||
@@ -242,7 +268,7 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
|
||||
if (link) {
|
||||
Log.withTick(
|
||||
`Linking shared variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
`Linking account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
@@ -257,7 +283,9 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
});
|
||||
|
||||
Log.withTick(
|
||||
`Linked shared variable ${chalk.bold(name)} to project ${chalk.bold(projectDisplayName)}.`
|
||||
`Linked account-wide variable ${chalk.bold(name)} to project ${chalk.bold(
|
||||
projectDisplayName
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -288,22 +316,31 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async promptForMissingFlagsAsync({
|
||||
name,
|
||||
value,
|
||||
environment,
|
||||
visibility = EnvironmentVariableVisibility.Public,
|
||||
'non-interactive': nonInteractive,
|
||||
type,
|
||||
...rest
|
||||
}: CreateFlags): Promise<
|
||||
Required<Omit<CreateFlags, 'type'> & { type: EnvironmentSecretType | undefined }>
|
||||
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 }
|
||||
> {
|
||||
if (!name) {
|
||||
name = await promptVariableNameAsync(nonInteractive);
|
||||
}
|
||||
|
||||
let newType;
|
||||
let newVisibility = visibility ? parseVisibility(visibility) : undefined;
|
||||
|
||||
if (type === 'file') {
|
||||
newType = EnvironmentSecretType.FileBase64;
|
||||
@@ -315,59 +352,78 @@ export default class EnvironmentVariableCreate extends EasCommand {
|
||||
newType = await promptVariableTypeAsync(nonInteractive);
|
||||
}
|
||||
|
||||
if (!newVisibility) {
|
||||
newVisibility = await promptVariableVisibilityAsync(nonInteractive);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
value = await promptVariableValueAsync({
|
||||
nonInteractive,
|
||||
hidden: visibility !== EnvironmentVariableVisibility.Public,
|
||||
hidden: newVisibility !== EnvironmentVariableVisibility.Public,
|
||||
filePath: newType === EnvironmentSecretType.FileBase64,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
value = environmentFilePath ? await fs.readFile(environmentFilePath, 'base64') : value;
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync({ nonInteractive, multiple: true });
|
||||
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,
|
||||
visibility,
|
||||
environment: newEnvironments,
|
||||
visibility: newVisibility,
|
||||
link: rest.link ?? false,
|
||||
force: rest.force ?? false,
|
||||
scope: rest.scope ?? EnvironmentVariableScope.Project,
|
||||
'non-interactive': nonInteractive,
|
||||
type: newType,
|
||||
fileName,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
private validateFlags(flags: CreateFlags & { type?: string }): CreateFlags {
|
||||
if (flags.scope !== EnvironmentVariableScope.Shared && flags.link) {
|
||||
private sanitizeFlags(flags: RawCreateFlags): CreateFlags {
|
||||
if (flags.scope !== 'account' && flags.link) {
|
||||
throw new Error(
|
||||
`Unexpected argument: --link can only be used when creating shared variables`
|
||||
`Unexpected argument: --link can only be used when creating account-wide variables`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
flags.scope === EnvironmentVariableScope.Shared &&
|
||||
flags.environment &&
|
||||
!flags.link &&
|
||||
flags['non-interactive']
|
||||
) {
|
||||
throw new Error(
|
||||
'Unexpected argument: --environment in non-interactive mode can only be used with --link flag.'
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
return {
|
||||
...flags,
|
||||
scope:
|
||||
flags.scope === 'account'
|
||||
? EnvironmentVariableScope.Shared
|
||||
: EnvironmentVariableScope.Project,
|
||||
};
|
||||
}
|
||||
}
|
||||
+50
-16
@@ -4,8 +4,9 @@ import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
|
||||
@@ -13,19 +14,24 @@ import { EnvironmentVariableMutation } from '../../graphql/mutations/Environment
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { formatVariableName } from '../../utils/variableUtils';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type DeleteFlags = {
|
||||
interface DeleteFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
scope?: EnvironmentVariableScope;
|
||||
};
|
||||
scope: EnvironmentVariableScope;
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable by name';
|
||||
interface RawDeleteFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvDelete extends EasCommand {
|
||||
static override description = 'delete an environment variable for the current project or account';
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
@@ -35,27 +41,36 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
...EasEnvironmentFlagParameters,
|
||||
description: 'Current environment of the variable to delete',
|
||||
}),
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableDelete);
|
||||
const { args, flags } = await this.parse(EnvDelete);
|
||||
const {
|
||||
'variable-name': name,
|
||||
'variable-environment': environment,
|
||||
'non-interactive': nonInteractive,
|
||||
scope,
|
||||
} = this.validateFlags(flags);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableDelete, {
|
||||
} = await this.getContextAsync(EnvDelete, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -130,7 +145,10 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
Log.withTick(`️Deleted variable ${selectedVariable.name}".`);
|
||||
}
|
||||
|
||||
private validateFlags(flags: DeleteFlags): DeleteFlags {
|
||||
private sanitizeInputs(
|
||||
flags: RawDeleteFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): DeleteFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error(
|
||||
@@ -141,6 +159,22 @@ export default class EnvironmentVariableDelete extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
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 };
|
||||
}
|
||||
}
|
||||
+49
-49
@@ -3,61 +3,63 @@ import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import { 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 bash command with environment variables from the selected environment';
|
||||
|
||||
static override hidden = true;
|
||||
'execute a command with environment variables from the selected environment';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override args = [
|
||||
{
|
||||
name: 'BASH_COMMAND',
|
||||
name: 'environment',
|
||||
required: true,
|
||||
description:
|
||||
"Environment to execute the command in. One of 'production', 'preview', or 'development'.",
|
||||
},
|
||||
{
|
||||
name: 'bash_command',
|
||||
required: true,
|
||||
description: 'bash command to execute with the environment variables from the environment',
|
||||
},
|
||||
];
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags,
|
||||
args: { BASH_COMMAND: command },
|
||||
} = await this.parse(EnvExec);
|
||||
const { flags, args } = await this.parse(EnvExec);
|
||||
|
||||
const parsedFlags = this.sanitizeFlags(flags);
|
||||
const parsedFlags = this.sanitizeFlagsAndArgs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvExec, {
|
||||
nonInteractive: parsedFlags.nonInteractive,
|
||||
@@ -72,25 +74,32 @@ export default class EnvExec extends EasCommand {
|
||||
environment,
|
||||
});
|
||||
|
||||
await this.runCommandWithEnvVarsAsync({ command, environmentVariables });
|
||||
await this.runCommandWithEnvVarsAsync({
|
||||
command: parsedFlags.command,
|
||||
environmentVariables,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
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'`."
|
||||
);
|
||||
}
|
||||
|
||||
environment = environment?.toUpperCase();
|
||||
|
||||
if (!isEnvironment(environment)) {
|
||||
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
|
||||
}
|
||||
|
||||
return {
|
||||
nonInteractive: false,
|
||||
environment: rawFlags.environment,
|
||||
nonInteractive: rawFlags['non-interactive'],
|
||||
environment,
|
||||
command: bash_command,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,29 +158,20 @@ export default class EnvExec extends EasCommand {
|
||||
environment,
|
||||
});
|
||||
|
||||
const secretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !value
|
||||
);
|
||||
if (secretEnvironmentVariables.length > 0) {
|
||||
Log.warn(`The following environment variables are secret and cannot be downloaded locally:`);
|
||||
for (const { name } of secretEnvironmentVariables) {
|
||||
Log.warn(`- ${name}`);
|
||||
}
|
||||
Log.warn('Proceeding with the rest of the environment variables.');
|
||||
Log.newLine();
|
||||
}
|
||||
|
||||
const nonSecretEnvironmentVariables = environmentVariablesQueryResult.filter(
|
||||
({ value }) => !!value
|
||||
);
|
||||
if (nonSecretEnvironmentVariables.length === 0) {
|
||||
throw new Error('No readable environment variables found for the selected environment.');
|
||||
}
|
||||
Log.log(
|
||||
`Loaded environment variables for the selected environment "${environment.toLowerCase()}":`
|
||||
);
|
||||
for (const { name } of nonSecretEnvironmentVariables) {
|
||||
Log.log(`- ${name}`);
|
||||
|
||||
if (nonSecretEnvironmentVariables.length > 0) {
|
||||
Log.log(
|
||||
`Environment variables with visibility "Plain text" and "Sensitive" loaded from the "${environment.toLowerCase()}" environment on EAS: ${nonSecretEnvironmentVariables
|
||||
.map(e => e.name)
|
||||
.join(', ')}.`
|
||||
);
|
||||
} else {
|
||||
Log.log(
|
||||
`No environment variables with visibility "Plain text" and "Sensitive" found for the "${environment.toLowerCase()}" environment on EAS servers.`
|
||||
);
|
||||
}
|
||||
Log.newLine();
|
||||
|
||||
|
||||
+66
-34
@@ -1,13 +1,13 @@
|
||||
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 {
|
||||
@@ -19,26 +19,41 @@ import {
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../log';
|
||||
import { promptVariableEnvironmentAsync, promptVariableNameAsync } from '../../utils/prompts';
|
||||
import { formatVariable, formatVariableValue } from '../../utils/variableUtils';
|
||||
import { formatVariable, formatVariableValue, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type GetFlags = {
|
||||
interface RawGetFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
format?: string;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
}
|
||||
|
||||
interface GetFlags {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
format?: string;
|
||||
scope: EnvironmentVariableScope;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableGet extends EasCommand {
|
||||
static override description = 'get environment variable';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvGet extends EasCommand {
|
||||
static override description = 'view an environment variable for the current project or account';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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',
|
||||
@@ -48,12 +63,12 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
description: 'Current environment of the variable',
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableGet);
|
||||
const { args, flags } = await this.parse(EnvGet);
|
||||
|
||||
let {
|
||||
'variable-environment': environment,
|
||||
@@ -61,12 +76,12 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
'non-interactive': nonInteractive,
|
||||
format,
|
||||
scope,
|
||||
} = this.validateFlags(flags);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableGet, {
|
||||
} = await this.getContextAsync(EnvGet, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -74,6 +89,13 @@ export default class EnvironmentVariableGet 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) {
|
||||
@@ -84,24 +106,11 @@ export default class EnvironmentVariableGet 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}"`);
|
||||
throw new Error(
|
||||
`Variable with name "${name}" not found in environment "${environment.toLocaleLowerCase()}"`
|
||||
);
|
||||
}
|
||||
|
||||
variable = variableInEnvironment;
|
||||
@@ -124,7 +133,7 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private validateFlags(flags: GetFlags): GetFlags {
|
||||
private sanitizeInputs(flags: RawGetFlags, { environment }: { environment?: string }): GetFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error('Variable name is required. Run the command with --variable-name flag.');
|
||||
@@ -132,11 +141,34 @@ export default class EnvironmentVariableGet extends EasCommand {
|
||||
if (!flags.scope) {
|
||||
throw new Error('Scope is required. Run the command with --scope flag.');
|
||||
}
|
||||
if (!flags['variable-environment']) {
|
||||
if (!(flags['variable-environment'] ?? environment)) {
|
||||
throw new Error('Environment is required.');
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+66
-15
@@ -14,11 +14,19 @@ import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
import { formatVariableName } from '../../utils/variableUtils';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
export default class EnvironmentVariableLink extends EasCommand {
|
||||
static override description = 'link a shared environment variable to the current project';
|
||||
type LinkFlags = {
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
};
|
||||
|
||||
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 = {
|
||||
@@ -33,24 +41,34 @@ export default class EnvironmentVariableLink 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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { args, flags } = await this.parse(EnvLink);
|
||||
|
||||
let {
|
||||
flags: {
|
||||
'variable-name': name,
|
||||
'variable-environment': currentEnvironment,
|
||||
'non-interactive': nonInteractive,
|
||||
environment: environments,
|
||||
},
|
||||
} = await this.parse(EnvironmentVariableLink);
|
||||
'variable-name': name,
|
||||
'variable-environment': currentEnvironment,
|
||||
'non-interactive': nonInteractive,
|
||||
environment: environments,
|
||||
} = this.validateInputs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableLink, {
|
||||
} = await this.getContextAsync(EnvLink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -64,8 +82,13 @@ export default class EnvironmentVariableLink 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 shared variable',
|
||||
'Select account-wide variable',
|
||||
variables.map(variable => ({
|
||||
title: formatVariableName(variable),
|
||||
value: variable,
|
||||
@@ -74,7 +97,7 @@ export default class EnvironmentVariableLink extends EasCommand {
|
||||
}
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
throw new Error(`Account-wide variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
let explicitSelect = false;
|
||||
@@ -150,4 +173,32 @@ export default class EnvironmentVariableLink 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
+78
-20
@@ -4,9 +4,10 @@ import chalk from 'chalk';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASVariableFormatFlag,
|
||||
EASVariableScopeFlag,
|
||||
} from '../../commandUtils/flags';
|
||||
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
|
||||
import {
|
||||
@@ -18,6 +19,7 @@ import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
import {
|
||||
formatVariable,
|
||||
formatVariableValue,
|
||||
isEnvironment,
|
||||
performForEnvironmentsAsync,
|
||||
} from '../../utils/variableUtils';
|
||||
|
||||
@@ -65,13 +67,29 @@ async function getVariablesForScopeAsync(
|
||||
});
|
||||
}
|
||||
|
||||
export default class EnvironmentValueList extends EasCommand {
|
||||
static override description = 'list environment variables for the current project';
|
||||
interface RawListFlags {
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
format: string;
|
||||
environment: EnvironmentVariableEnvironment[] | undefined;
|
||||
'include-sensitive': boolean;
|
||||
'include-file-content': boolean;
|
||||
'non-interactive'?: boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
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 contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -84,26 +102,36 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
description: 'Display files content in the output',
|
||||
default: false,
|
||||
}),
|
||||
...EASVariableFormatFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
...EASVariableFormatFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
};
|
||||
|
||||
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 {
|
||||
flags: {
|
||||
environment: environments,
|
||||
format,
|
||||
scope,
|
||||
'include-sensitive': includeSensitive,
|
||||
'include-file-content': includeFileContent,
|
||||
'non-interactive': nonInteractive,
|
||||
},
|
||||
} = await this.parse(EnvironmentValueList);
|
||||
format,
|
||||
environment: environments,
|
||||
scope,
|
||||
'include-sensitive': includeSensitive,
|
||||
'include-file-content': includeFileContent,
|
||||
'non-interactive': nonInteractive,
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentValueList, {
|
||||
} = await this.getContextAsync(EnvList, {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
@@ -122,7 +150,12 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
if (environment) {
|
||||
Log.log(chalk.bold(`Environment: ${environment}`));
|
||||
Log.log(chalk.bold(`Environment: ${environment.toLocaleLowerCase()}`));
|
||||
}
|
||||
|
||||
if (variables.length === 0) {
|
||||
Log.log('No variables found for this environment.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === 'short') {
|
||||
@@ -131,7 +164,7 @@ export default class EnvironmentValueList extends EasCommand {
|
||||
}
|
||||
} else {
|
||||
if (scope === EnvironmentVariableScope.Shared) {
|
||||
Log.log(chalk.bold('Shared variables for this account:'));
|
||||
Log.log(chalk.bold('Account-wide variables for this account:'));
|
||||
} else {
|
||||
Log.log(chalk.bold(`Variables for this project:`));
|
||||
}
|
||||
@@ -141,4 +174,29 @@ export default class EnvironmentValueList 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
+87
-31
@@ -1,27 +1,42 @@
|
||||
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 { EnvironmentVariableFragment } from '../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import { EnvironmentSecretType, EnvironmentVariableVisibility } from '../../graphql/generated';
|
||||
import {
|
||||
EnvironmentVariableWithFileContent,
|
||||
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 EnvironmentVariablePull extends EasCommand {
|
||||
static override description = 'pull env file';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvPull extends EasCommand {
|
||||
static override description =
|
||||
'pull environment variables for the selected environment to .env file';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 = {
|
||||
...EASEnvironmentFlag,
|
||||
...EASNonInteractiveFlag,
|
||||
...EASEnvironmentFlag,
|
||||
path: Flags.string({
|
||||
description: 'Path to the result `.env` file',
|
||||
default: '.env.local',
|
||||
@@ -30,16 +45,25 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
let {
|
||||
flags: { environment, path: targetPath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvironmentVariablePull);
|
||||
args: { environment: argEnvironment },
|
||||
flags: { environment: flagEnvironment, path: targetPath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvPull);
|
||||
|
||||
let environment = flagEnvironment?.toUpperCase() ?? argEnvironment?.toUpperCase();
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync({ nonInteractive });
|
||||
}
|
||||
|
||||
if (!isEnvironment(environment)) {
|
||||
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
|
||||
}
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariablePull, {
|
||||
projectDir,
|
||||
} = await this.getContextAsync(EnvPull, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -50,6 +74,7 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
{
|
||||
appId: projectId,
|
||||
environment,
|
||||
includeFileContent: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -63,32 +88,63 @@ export default class EnvironmentVariablePull extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const filePrefix = `# Environment: ${environment}\n\n`;
|
||||
let currentEnvLocal: Record<string, string> = {};
|
||||
|
||||
const envFileContent = environmentVariables
|
||||
.map((variable: EnvironmentVariableFragment) => {
|
||||
if (variable.value === null) {
|
||||
return `# ${variable.name}=***** (secret variables are not available for reading)`;
|
||||
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}`;
|
||||
}
|
||||
return `${variable.name}=${variable.value}`;
|
||||
})
|
||||
.join('\n');
|
||||
await fs.writeFile(targetPath, filePrefix + envFileContent);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
await fs.writeFile(targetPath, filePrefix + envFileContentLines.join('\n'));
|
||||
|
||||
Log.log(
|
||||
`Pulled environment variables from ${environment.toLowerCase()} environment to ${targetPath}.`
|
||||
`Pulled plain text and sensitive environment variables from "${environment.toLowerCase()}" environment to ${targetPath}.`
|
||||
);
|
||||
|
||||
if (overridenSecretVariables.length > 0) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(`Reused local values for following secrets: ${overridenSecretVariables.join('\n')}.`);
|
||||
}
|
||||
|
||||
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(
|
||||
', '
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
-114
@@ -1,9 +1,10 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import dotenv from 'dotenv';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASEnvironmentFlag } from '../../commandUtils/flags';
|
||||
import { EASMultiEnvironmentFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
EnvironmentVariableEnvironment,
|
||||
EnvironmentVariableFragment,
|
||||
@@ -17,145 +18,168 @@ 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';
|
||||
|
||||
type PushFlags = {
|
||||
environment?: EnvironmentVariableEnvironment;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export default class EnvironmentVariablePush extends EasCommand {
|
||||
static override description = 'push env file';
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvPush extends EasCommand {
|
||||
static override description =
|
||||
'push environment variables from .env file to the selected environment';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASEnvironmentFlag,
|
||||
...EASMultiEnvironmentFlag,
|
||||
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 { flags } = await this.parse(EnvironmentVariablePush);
|
||||
const { args, flags } = await this.parse(EnvPush);
|
||||
|
||||
let { environment: environments, path: envPath } = this.parseFlagsAndArgs(flags, args);
|
||||
|
||||
const { environment, path: envPath } = this.validateFlags(flags);
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariablePush, {
|
||||
} = await this.getContextAsync(EnvPush, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
if (!environments) {
|
||||
environments = await promptVariableEnvironmentAsync({
|
||||
nonInteractive: false,
|
||||
multiple: true,
|
||||
});
|
||||
}
|
||||
|
||||
const updateVariables: Record<string, EnvironmentVariablePushInput> =
|
||||
await this.parseEnvFileAsync(envPath, environment);
|
||||
await this.parseEnvFileAsync(envPath, environments);
|
||||
|
||||
const variableNames = Object.keys(updateVariables);
|
||||
|
||||
const existingVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment,
|
||||
filterNames: variableNames,
|
||||
});
|
||||
for (const environment of environments) {
|
||||
const displayedEnvironment = environment.toLocaleLowerCase();
|
||||
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(variable => {
|
||||
const existingVariableUpdate = updateVariables[variable.name];
|
||||
if (existingVariableUpdate && existingVariableUpdate.value !== variable.value) {
|
||||
existingDifferentVariables.push(variable);
|
||||
} else {
|
||||
delete updateVariables[variable.name];
|
||||
}
|
||||
});
|
||||
const existingDifferentVariables: EnvironmentVariableFragment[] = [];
|
||||
// Remove variables that are the same as the ones in the environment
|
||||
existingVariables.forEach(existingVariable => {
|
||||
const existingVariableUpdate = updateVariables[existingVariable.name];
|
||||
|
||||
const existingDifferentSharedVariables = existingDifferentVariables.filter(
|
||||
variable => variable.scope === EnvironmentVariableScope.Shared
|
||||
);
|
||||
if (existingVariableUpdate) {
|
||||
const hasMoreEnvironments = existingVariableUpdate.environments.some(
|
||||
newEnv => !existingVariable.environments?.includes(newEnv)
|
||||
);
|
||||
|
||||
if (existingDifferentSharedVariables.length > 0) {
|
||||
const existingDifferentSharedVariablesNames = existingDifferentSharedVariables.map(
|
||||
variable => variable.name
|
||||
if (existingVariableUpdate.value !== existingVariable.value || hasMoreEnvironments) {
|
||||
existingDifferentVariables.push(existingVariable);
|
||||
} else {
|
||||
delete updateVariables[existingVariable.name];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const existingDifferentSharedVariables = existingDifferentVariables.filter(
|
||||
variable => variable.scope === EnvironmentVariableScope.Shared
|
||||
);
|
||||
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 (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 (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,
|
||||
})),
|
||||
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}`);
|
||||
});
|
||||
variablesToOverwrite = promptResult.variablesToOverwrite;
|
||||
throw new Error('Account-wide variables cannot be overwritten by eas env:push command');
|
||||
}
|
||||
|
||||
for (const existingVariable of existingVariables) {
|
||||
const name = existingVariable.name;
|
||||
if (variablesToOverwrite.includes(name)) {
|
||||
updateVariables[name]['overwrite'] = true;
|
||||
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,
|
||||
});
|
||||
|
||||
let variablesToOverwrite: string[] = [];
|
||||
|
||||
if (!confirm && existingDifferentVariables.length === 0) {
|
||||
throw new Error('No new variables to push.');
|
||||
}
|
||||
|
||||
if (confirm) {
|
||||
variablesToOverwrite = existingDifferentVariables.map(variable => variable.name);
|
||||
} else {
|
||||
delete updateVariables[name];
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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}`
|
||||
// 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
|
||||
);
|
||||
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...');
|
||||
|
||||
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...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,13 +195,30 @@ export default class EnvironmentVariablePush extends EasCommand {
|
||||
variablesToPush,
|
||||
projectId
|
||||
);
|
||||
Log.log(`Uploaded env file to ${environments.join(', ').toLocaleLowerCase()}.`);
|
||||
}
|
||||
|
||||
Log.log(`Uploaded env file to ${environment} environment.`);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
private async parseEnvFileAsync(
|
||||
envPath: string,
|
||||
environment: string
|
||||
environments: EnvironmentVariableEnvironment[]
|
||||
): Promise<Record<string, EnvironmentVariablePushInput>> {
|
||||
if (!(await fs.exists(envPath))) {
|
||||
throw new Error(`File ${envPath} does not exist.`);
|
||||
@@ -186,11 +227,25 @@ export default class EnvironmentVariablePush 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,
|
||||
environment,
|
||||
environments,
|
||||
visibility: name.startsWith('EXPO_SENSITIVE')
|
||||
? EnvironmentVariableVisibility.Sensitive
|
||||
: EnvironmentVariableVisibility.Public,
|
||||
@@ -199,12 +254,4 @@ export default class EnvironmentVariablePush 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 };
|
||||
}
|
||||
}
|
||||
+60
-14
@@ -10,11 +10,19 @@ import Log from '../../log';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
|
||||
import { formatVariableName } from '../../utils/variableUtils';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
static override description = 'unlink a shared environment variable to the current project';
|
||||
type UnlinkFlags = {
|
||||
'variable-name'?: string;
|
||||
'non-interactive': boolean;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
};
|
||||
|
||||
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 = {
|
||||
@@ -26,22 +34,32 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...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 {
|
||||
flags: {
|
||||
'variable-name': name,
|
||||
'non-interactive': nonInteractive,
|
||||
environment: unlinkEnvironments,
|
||||
},
|
||||
} = await this.parse(EnvironmentVariableUnlink);
|
||||
'variable-name': name,
|
||||
'non-interactive': nonInteractive,
|
||||
environment: unlinkEnvironments,
|
||||
} = this.validateInputs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUnlink, {
|
||||
} = await this.getContextAsync(EnvUnlink, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -58,7 +76,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
throw new Error("Multiple variables found, please select one using '--variable-name'");
|
||||
}
|
||||
selectedVariable = await selectAsync(
|
||||
'Select shared variable',
|
||||
'Select account-wide variable',
|
||||
variables.map(variable => ({
|
||||
title: formatVariableName(variable),
|
||||
value: variable,
|
||||
@@ -67,7 +85,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
|
||||
}
|
||||
|
||||
if (!selectedVariable) {
|
||||
throw new Error(`Shared variable ${name} doesn't exist`);
|
||||
throw new Error(`Account-wide variable ${name} doesn't exist`);
|
||||
}
|
||||
|
||||
let explicitSelect = false;
|
||||
@@ -143,4 +161,32 @@ export default class EnvironmentVariableUnlink 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;
|
||||
}
|
||||
}
|
||||
+87
-36
@@ -6,9 +6,10 @@ import path from 'path';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import {
|
||||
EASEnvironmentVariableScopeFlag,
|
||||
EASEnvironmentVariableScopeFlagValue,
|
||||
EASMultiEnvironmentFlag,
|
||||
EASNonInteractiveFlag,
|
||||
EASVariableScopeFlag,
|
||||
EASVariableVisibilityFlag,
|
||||
EasEnvironmentFlagParameters,
|
||||
} from '../../commandUtils/flags';
|
||||
@@ -28,31 +29,41 @@ import {
|
||||
} from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import {
|
||||
parseVisibility,
|
||||
promptVariableEnvironmentAsync,
|
||||
promptVariableNameAsync,
|
||||
promptVariableTypeAsync,
|
||||
promptVariableValueAsync,
|
||||
promptVariableVisibilityAsync,
|
||||
} from '../../utils/prompts';
|
||||
import { formatVariableName } from '../../utils/variableUtils';
|
||||
import { formatVariableName, isEnvironment } from '../../utils/variableUtils';
|
||||
|
||||
type UpdateFlags = {
|
||||
interface RawUpdateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
scope?: EnvironmentVariableScope;
|
||||
scope: EASEnvironmentVariableScopeFlagValue;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
visibility?: EnvironmentVariableVisibility;
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
type?: 'string' | 'file';
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
static override description =
|
||||
'update an environment variable on the current project or owner account';
|
||||
interface UpdateFlags {
|
||||
name?: string;
|
||||
value?: string;
|
||||
scope: EnvironmentVariableScope;
|
||||
environment?: EnvironmentVariableEnvironment[];
|
||||
visibility?: 'plaintext' | 'sensitive' | 'secret';
|
||||
type?: 'string' | 'file';
|
||||
'variable-name'?: string;
|
||||
'variable-environment'?: EnvironmentVariableEnvironment;
|
||||
'non-interactive': boolean;
|
||||
}
|
||||
|
||||
static override hidden = true;
|
||||
export default class EnvUpdate extends EasCommand {
|
||||
static override description = 'update an environment variable on the current project or account';
|
||||
|
||||
static override flags = {
|
||||
'variable-name': Flags.string({
|
||||
@@ -73,19 +84,28 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
options: ['string', 'file'],
|
||||
}),
|
||||
...EASVariableVisibilityFlag,
|
||||
...EASVariableScopeFlag,
|
||||
...EASEnvironmentVariableScopeFlag,
|
||||
...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.ProjectConfig,
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.Analytics,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(EnvironmentVariableUpdate);
|
||||
const { args, flags } = await this.parse(EnvUpdate);
|
||||
const {
|
||||
name,
|
||||
value: rawValue,
|
||||
@@ -96,12 +116,12 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
environment: environments,
|
||||
type,
|
||||
visibility,
|
||||
} = this.validateFlags(flags);
|
||||
} = this.sanitizeInputs(flags, args);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(EnvironmentVariableUpdate, {
|
||||
} = await this.getContextAsync(EnvUpdate, {
|
||||
nonInteractive,
|
||||
});
|
||||
|
||||
@@ -136,7 +156,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
if (existingVariables.length === 0) {
|
||||
throw new Error(
|
||||
`Variable with name ${currentName} ${
|
||||
currentEnvironment ? `in environment ${currentEnvironment}` : ''
|
||||
currentEnvironment ? `in environment ${currentEnvironment.toLocaleLowerCase()}` : ''
|
||||
} does not exist ${suffix}.`
|
||||
);
|
||||
} else if (existingVariables.length > 1) {
|
||||
@@ -159,6 +179,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
environment: newEnvironments,
|
||||
visibility: newVisibility,
|
||||
type: newType,
|
||||
fileName,
|
||||
} = await this.promptForMissingFlagsAsync(selectedVariable, {
|
||||
name,
|
||||
value: rawValue,
|
||||
@@ -166,6 +187,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
visibility,
|
||||
'non-interactive': nonInteractive,
|
||||
type,
|
||||
scope,
|
||||
});
|
||||
|
||||
const variable = await EnvironmentVariableMutation.updateAsync(graphqlClient, {
|
||||
@@ -175,6 +197,7 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
environments: newEnvironments,
|
||||
type: newType,
|
||||
visibility: newVisibility,
|
||||
fileName: newValue ? fileName : undefined,
|
||||
});
|
||||
if (!variable) {
|
||||
throw new Error(`Could not update variable with name ${name} ${suffix}`);
|
||||
@@ -182,7 +205,10 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
|
||||
Log.withTick(`Updated variable ${chalk.bold(selectedVariable.name)} ${suffix}.`);
|
||||
}
|
||||
private validateFlags(flags: UpdateFlags): UpdateFlags {
|
||||
private sanitizeInputs(
|
||||
flags: RawUpdateFlags,
|
||||
{ environment }: { environment?: string }
|
||||
): UpdateFlags {
|
||||
if (flags['non-interactive']) {
|
||||
if (!flags['variable-name']) {
|
||||
throw new Error(
|
||||
@@ -194,7 +220,26 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
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(
|
||||
@@ -208,8 +253,16 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
type,
|
||||
...rest
|
||||
}: UpdateFlags
|
||||
): Promise<Omit<UpdateFlags, 'type'> & { type?: EnvironmentSecretType }> {
|
||||
): 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;
|
||||
@@ -226,23 +279,15 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
}
|
||||
}
|
||||
|
||||
Log.log(
|
||||
selectedVariable.type,
|
||||
EnvironmentSecretType.String === selectedVariable.type,
|
||||
EnvironmentSecretType.FileBase64 === selectedVariable.type
|
||||
);
|
||||
if (!type && !value && !nonInteractive) {
|
||||
newType = await promptVariableTypeAsync(nonInteractive, selectedVariable.type);
|
||||
|
||||
if (!newType || newType === selectedVariable.type) {
|
||||
newType = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
value = await promptVariableValueAsync({
|
||||
nonInteractive,
|
||||
required: false,
|
||||
filePath: (newType ?? selectedVariable.type) === EnvironmentSecretType.FileBase64,
|
||||
initial:
|
||||
(newType ?? selectedVariable.type) === EnvironmentSecretType.FileBase64
|
||||
? undefined
|
||||
@@ -251,16 +296,18 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
|
||||
if (!value || value.length === 0 || value === selectedVariable.value) {
|
||||
value = undefined;
|
||||
newType = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let environmentFilePath: string | undefined;
|
||||
|
||||
if (newType === EnvironmentSecretType.FileBase64 && value) {
|
||||
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;
|
||||
@@ -282,26 +329,30 @@ export default class EnvironmentVariableUpdate extends EasCommand {
|
||||
}
|
||||
|
||||
if (!visibility) {
|
||||
visibility = await promptVariableVisibilityAsync(
|
||||
newVisibility = await promptVariableVisibilityAsync(
|
||||
nonInteractive,
|
||||
selectedVariable.visibility
|
||||
);
|
||||
|
||||
if (!visibility || visibility === selectedVariable.visibility) {
|
||||
visibility = undefined;
|
||||
if (!newVisibility || newVisibility === selectedVariable.visibility) {
|
||||
newVisibility = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visibility) {
|
||||
newVisibility = parseVisibility(visibility);
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
name,
|
||||
value,
|
||||
environment: environments,
|
||||
visibility,
|
||||
scope: rest.scope ?? EnvironmentVariableScope.Project,
|
||||
visibility: newVisibility,
|
||||
'non-interactive': nonInteractive,
|
||||
type: newType,
|
||||
...rest,
|
||||
fileName,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { fetchBuildsAsync, formatBuild } from '../../commandUtils/builds';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { AppPlatform, BuildStatus } from '../../graphql/generated';
|
||||
import { BuildQuery } from '../../graphql/queries/BuildQuery';
|
||||
import Log from '../../log';
|
||||
import { ora } from '../../ora';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { Fingerprint, FingerprintDiffItem } from '../../utils/fingerprint';
|
||||
import { createFingerprintAsync, diffFingerprint } from '../../utils/fingerprintCli';
|
||||
import { abridgedDiff } from '../../utils/fingerprintDiff';
|
||||
import formatFields, { FormatFieldsItem } from '../../utils/formatFields';
|
||||
import { enableJsonOutput } from '../../utils/json';
|
||||
|
||||
export default class FingerprintCompare extends EasCommand {
|
||||
static override description = 'compare fingerprints of the current project, builds and updates';
|
||||
static override hidden = true;
|
||||
|
||||
static override flags = {
|
||||
'build-id': Flags.string({
|
||||
aliases: ['buildId'],
|
||||
description: 'Compare the fingerprint with the build with the specified ID',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Vcs,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(FingerprintCompare);
|
||||
const { json: jsonFlag, 'non-interactive': nonInteractive, buildId: buildIdFromArg } = flags;
|
||||
|
||||
const {
|
||||
projectId,
|
||||
privateProjectConfig: { projectDir },
|
||||
loggedIn: { graphqlClient },
|
||||
vcsClient,
|
||||
} = await this.getContextAsync(FingerprintCompare, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const displayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
|
||||
let buildId: string | null = buildIdFromArg;
|
||||
if (!buildId) {
|
||||
if (nonInteractive) {
|
||||
throw new Error('Build ID must be provided in non-interactive mode');
|
||||
}
|
||||
|
||||
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
||||
filters: { hasFingerprint: true },
|
||||
});
|
||||
if (!buildId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
||||
const buildWithFingerprint = await BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
||||
const fingerprintDebugUrl = buildWithFingerprint.fingerprint?.debugInfoUrl;
|
||||
if (!fingerprintDebugUrl) {
|
||||
Log.error('A fingerprint for the build could not be found.');
|
||||
return;
|
||||
}
|
||||
const fingerprintResponse = await fetch(fingerprintDebugUrl);
|
||||
const fingerprint = (await fingerprintResponse.json()) as Fingerprint;
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const buildPlatform = buildWithFingerprint.platform;
|
||||
const workflow = workflows[appPlatformToPlatform(buildPlatform)];
|
||||
|
||||
const projectFingerprint = await createFingerprintAsync(projectDir, {
|
||||
workflow,
|
||||
platforms: [appPlatformToString(buildPlatform)],
|
||||
debug: true,
|
||||
env: undefined,
|
||||
});
|
||||
if (!projectFingerprint) {
|
||||
Log.error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
||||
return;
|
||||
}
|
||||
if (fingerprint.hash === projectFingerprint.hash) {
|
||||
Log.log(`✅ Project fingerprint matches build`);
|
||||
return;
|
||||
} else {
|
||||
Log.log(`🔄 Project fingerprint differs from build`);
|
||||
}
|
||||
|
||||
const fingerprintDiffs = diffFingerprint(projectDir, fingerprint, projectFingerprint);
|
||||
if (!fingerprintDiffs) {
|
||||
Log.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
||||
return;
|
||||
}
|
||||
|
||||
const filePathDiffs = fingerprintDiffs.filter(diff => {
|
||||
let sourceType;
|
||||
if (diff.op === 'added') {
|
||||
sourceType = diff.addedSource.type;
|
||||
} else if (diff.op === 'removed') {
|
||||
sourceType = diff.removedSource.type;
|
||||
} else if (diff.op === 'changed') {
|
||||
sourceType = diff.beforeSource.type;
|
||||
}
|
||||
return sourceType === 'dir' || sourceType === 'file';
|
||||
});
|
||||
if (filePathDiffs.length > 0) {
|
||||
Log.newLine();
|
||||
Log.log('📁 Paths with native dependencies:');
|
||||
}
|
||||
const fields = [];
|
||||
for (const diff of filePathDiffs) {
|
||||
const field = getDiffFilePathFields(diff);
|
||||
if (!field) {
|
||||
throw new Error(`Unsupported diff: ${JSON.stringify(diff)}`);
|
||||
}
|
||||
fields.push(field);
|
||||
}
|
||||
Log.log(
|
||||
formatFields(fields, {
|
||||
labelFormat: label => ` ${chalk.dim(label)}:`,
|
||||
})
|
||||
);
|
||||
|
||||
const contentDiffs = fingerprintDiffs.filter(diff => {
|
||||
let sourceType;
|
||||
if (diff.op === 'added') {
|
||||
sourceType = diff.addedSource.type;
|
||||
} else if (diff.op === 'removed') {
|
||||
sourceType = diff.removedSource.type;
|
||||
} else if (diff.op === 'changed') {
|
||||
sourceType = diff.beforeSource.type;
|
||||
}
|
||||
return sourceType === 'contents';
|
||||
});
|
||||
|
||||
for (const diff of contentDiffs) {
|
||||
printContentDiff(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printContentDiff(diff: FingerprintDiffItem): void {
|
||||
if (diff.op === 'added') {
|
||||
const sourceType = diff.addedSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
contentsId: diff.addedSource.id,
|
||||
contentsAfter: diff.addedSource.contents,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'removed') {
|
||||
const sourceType = diff.removedSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
contentsId: diff.removedSource.id,
|
||||
contentsBefore: diff.removedSource.contents,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'changed') {
|
||||
const sourceType = diff.beforeSource.type;
|
||||
if (sourceType === 'contents') {
|
||||
if (diff.afterSource.type !== 'contents') {
|
||||
throw new Error(
|
||||
`Changed fingerprint source types must be the same, received ${diff.beforeSource.type}, ${diff.afterSource.type}`
|
||||
);
|
||||
}
|
||||
printContentSource({
|
||||
op: diff.op,
|
||||
sourceType: diff.beforeSource.type, // before and after source types should be the same
|
||||
contentsId: diff.beforeSource.id, // before and after content ids should be the same
|
||||
contentsBefore: diff.beforeSource.contents,
|
||||
contentsAfter: diff.afterSource.contents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDiffFilePathFields(diff: FingerprintDiffItem): FormatFieldsItem | null {
|
||||
if (diff.op === 'added') {
|
||||
const sourceType = diff.addedSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
filePath: diff.addedSource.filePath,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'removed') {
|
||||
const sourceType = diff.removedSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType,
|
||||
filePath: diff.removedSource.filePath,
|
||||
});
|
||||
}
|
||||
} else if (diff.op === 'changed') {
|
||||
const sourceType = diff.beforeSource.type;
|
||||
if (sourceType !== 'contents') {
|
||||
return getFilePathSourceFields({
|
||||
op: diff.op,
|
||||
sourceType: diff.beforeSource.type, // before and after source types should be the same
|
||||
filePath: diff.beforeSource.filePath, // before and after filePaths should be the same
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFilePathSourceFields({
|
||||
op,
|
||||
sourceType,
|
||||
filePath,
|
||||
}: {
|
||||
op: 'added' | 'removed' | 'changed';
|
||||
sourceType: 'dir' | 'file';
|
||||
filePath: string;
|
||||
}): FormatFieldsItem {
|
||||
if (sourceType === 'dir') {
|
||||
if (op === 'added') {
|
||||
return { label: 'new directory', value: filePath };
|
||||
} else if (op === 'removed') {
|
||||
return { label: 'removed directory', value: filePath };
|
||||
} else if (op === 'changed') {
|
||||
return { label: 'modified directory', value: filePath };
|
||||
}
|
||||
} else if (sourceType === 'file') {
|
||||
if (op === 'added') {
|
||||
return { label: 'new file', value: filePath };
|
||||
} else if (op === 'removed') {
|
||||
return { label: 'removed file', value: filePath };
|
||||
} else if (op === 'changed') {
|
||||
return { label: 'modified file', value: filePath };
|
||||
}
|
||||
}
|
||||
throw new Error(`Unsupported source and op: ${sourceType}, ${op}`);
|
||||
}
|
||||
|
||||
const PRETTY_CONTENT_ID: Record<string, string> = {
|
||||
'expoAutolinkingConfig:ios': 'Expo autolinking config (iOS)',
|
||||
'expoAutolinkingConfig:android': 'Expo autolinking config (Android)',
|
||||
'packageJson:scripts': 'package.json scripts',
|
||||
expoConfig: 'Expo config',
|
||||
};
|
||||
|
||||
function printContentSource({
|
||||
op,
|
||||
contentsBefore,
|
||||
contentsAfter,
|
||||
contentsId,
|
||||
}: {
|
||||
op: 'added' | 'removed' | 'changed';
|
||||
sourceType: 'contents';
|
||||
contentsBefore?: string | Buffer;
|
||||
contentsAfter?: string | Buffer;
|
||||
contentsId: string;
|
||||
}): void {
|
||||
Log.newLine();
|
||||
const prettyContentId = PRETTY_CONTENT_ID[contentsId] ?? contentsId;
|
||||
if (op === 'added') {
|
||||
Log.log(`${chalk.dim('📝 New content')}: ${prettyContentId}`);
|
||||
} else if (op === 'removed') {
|
||||
Log.log(`${chalk.dim('📝 Removed content')}: ${prettyContentId}`);
|
||||
} else if (op === 'changed') {
|
||||
Log.log(`${chalk.dim('📝 Modified content')}: ${prettyContentId}`);
|
||||
}
|
||||
printContentsDiff(contentsBefore ?? '', contentsAfter ?? '');
|
||||
}
|
||||
|
||||
function printContentsDiff(contents1: string | Buffer, contents2: string | Buffer): void {
|
||||
const stringifiedContents1 = Buffer.isBuffer(contents1) ? contents1.toString() : contents1;
|
||||
const stringifiedContents2 = Buffer.isBuffer(contents2) ? contents2.toString() : contents2;
|
||||
|
||||
const isStr1JSON = isJSON(stringifiedContents1);
|
||||
const isStr2JSON = isJSON(stringifiedContents2);
|
||||
|
||||
const prettifiedContents1 = isStr1JSON
|
||||
? JSON.stringify(JSON.parse(stringifiedContents1), null, 2)
|
||||
: stringifiedContents1;
|
||||
const prettifiedContents2 = isStr2JSON
|
||||
? JSON.stringify(JSON.parse(stringifiedContents2), null, 2)
|
||||
: stringifiedContents2;
|
||||
|
||||
abridgedDiff(prettifiedContents1, prettifiedContents2, 0);
|
||||
}
|
||||
|
||||
function isJSON(str: string): boolean {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function appPlatformToPlatform(platform: AppPlatform): Platform {
|
||||
switch (platform) {
|
||||
case AppPlatform.Android:
|
||||
return Platform.ANDROID;
|
||||
case AppPlatform.Ios:
|
||||
return Platform.IOS;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function appPlatformToString(platform: AppPlatform): string {
|
||||
switch (platform) {
|
||||
case AppPlatform.Android:
|
||||
return 'android';
|
||||
case AppPlatform.Ios:
|
||||
return 'ios';
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function selectBuildToCompareAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string,
|
||||
projectDisplayName: string,
|
||||
{
|
||||
filters,
|
||||
}: {
|
||||
filters?: {
|
||||
statuses?: BuildStatus[];
|
||||
platform?: RequestedPlatform;
|
||||
profile?: string;
|
||||
hasFingerprint?: boolean;
|
||||
};
|
||||
} = {}
|
||||
): Promise<string | null> {
|
||||
const spinner = ora().start('Fetching builds…');
|
||||
|
||||
let builds;
|
||||
try {
|
||||
builds = await fetchBuildsAsync({ graphqlClient, projectId, filters });
|
||||
spinner.stop();
|
||||
} catch (error) {
|
||||
spinner.fail(
|
||||
`Something went wrong and we couldn't fetch the builds for the project ${projectDisplayName}.`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (builds.length === 0) {
|
||||
Log.warn(`No fingerprints have been computed for builds of project ${projectDisplayName}.`);
|
||||
return null;
|
||||
} else {
|
||||
const build = await selectAsync<string>(
|
||||
'Which build do you want to compare?',
|
||||
builds.map(build => ({
|
||||
title: formatBuild(build),
|
||||
value: build.id,
|
||||
}))
|
||||
);
|
||||
|
||||
return build;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ 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,6 +39,7 @@ 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 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user