mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72cff1df1a | ||
|
|
6912e453ef | ||
|
|
839cbac47b | ||
|
|
5ba16dbc67 | ||
|
|
896b456dd3 | ||
|
|
f2c0202f06 | ||
|
|
497bac1cbf | ||
|
|
5b9734b60b | ||
|
|
3ac70d8977 | ||
|
|
32adc788d0 | ||
|
|
e465ed57cb |
No files matched your search
+28
-1
@@ -8,11 +8,38 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Improvements to workflow:create. ([#3194](https://github.com/expo/eas-cli/pull/3194) by [@douglowder](https://github.com/douglowder))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
- Fix --environment flag in eas env commands ([#3213](https://github.com/expo/eas-cli/pull/3213) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [16.21.0](https://github.com/expo/eas-cli/releases/tag/v16.21.0) - 2025-10-13
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Support `_expo/.routes.json` metadata file exported by `@expo/cli` for headers configuration on static deployments ([#3211](https://github.com/expo/eas-cli/pull/3211) by [@kitten](https://github.com/kitten))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add thorough tests for the env commands ([#3217](https://github.com/expo/eas-cli/pull/3217) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
## [16.20.4](https://github.com/expo/eas-cli/releases/tag/v16.20.4) - 2025-10-08
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Allow workflow:run to run a workflow from a GitHub ref. ([#3203](https://github.com/expo/eas-cli/pull/3203) by [@douglowder](https://github.com/douglowder))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix --environment flag in eas exec and pull ([#3216](https://github.com/expo/eas-cli/pull/3216) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
## [16.20.3](https://github.com/expo/eas-cli/releases/tag/v16.20.3) - 2025-10-07
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix --environment flag in eas env commands ([#3213](https://github.com/expo/eas-cli/pull/3213) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
## [16.20.2](https://github.com/expo/eas-cli/releases/tag/v16.20.2) - 2025-10-07
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "16.20.3",
|
||||
"version": "16.22.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+98
-90
@@ -173,7 +173,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -190,7 +190,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -207,7 +207,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -221,7 +221,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -273,7 +273,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -294,7 +294,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -314,7 +314,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -334,7 +334,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -357,7 +357,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -398,7 +398,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -417,7 +417,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -434,7 +434,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -453,7 +453,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:dev`
|
||||
|
||||
@@ -473,7 +473,7 @@ DESCRIPTION
|
||||
run dev client simulator/emulator build with matching fingerprint or create a new one
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:download`
|
||||
|
||||
@@ -494,7 +494,7 @@ DESCRIPTION
|
||||
download simulator/emulator builds for a given fingerprint hash
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/build/download.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -529,7 +529,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -581,7 +581,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -610,7 +610,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -636,7 +636,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -690,7 +690,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -709,7 +709,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -728,7 +728,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -745,7 +745,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -766,7 +766,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:delete [NAME]`
|
||||
|
||||
@@ -787,7 +787,7 @@ DESCRIPTION
|
||||
Delete a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -809,7 +809,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -829,7 +829,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -851,7 +851,7 @@ DESCRIPTION
|
||||
pause a channel to stop it from sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -873,7 +873,7 @@ DESCRIPTION
|
||||
resume a channel to start sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -906,7 +906,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -929,7 +929,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -950,7 +950,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/v16.20.3/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -967,7 +967,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -985,7 +985,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [options]`
|
||||
|
||||
@@ -1013,7 +1013,7 @@ ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
@@ -1038,7 +1038,7 @@ ALIASES
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
|
||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||
|
||||
@@ -1059,7 +1059,7 @@ ALIASES
|
||||
$ eas worker:alias:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
|
||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||
|
||||
@@ -1080,7 +1080,7 @@ ALIASES
|
||||
$ eas worker:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
@@ -1117,7 +1117,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -1137,7 +1137,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -1158,7 +1158,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -1179,7 +1179,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -1193,7 +1193,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -1207,7 +1207,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1236,7 +1236,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1260,7 +1260,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1281,7 +1281,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1306,7 +1306,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1331,7 +1331,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1354,7 +1354,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1375,7 +1375,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1406,7 +1406,7 @@ 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/v16.20.3/packages/eas-cli/src/commands/env/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||
|
||||
@@ -1451,7 +1451,7 @@ EXAMPLES
|
||||
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas fingerprint:generate`
|
||||
|
||||
@@ -1482,7 +1482,7 @@ EXAMPLES
|
||||
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1590,7 +1590,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1607,7 +1607,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1624,7 +1624,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1654,7 +1654,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/v16.20.3/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1668,7 +1668,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1691,7 +1691,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1709,7 +1709,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1744,7 +1744,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1786,7 +1786,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1806,7 +1806,7 @@ 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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1827,7 +1827,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -1851,7 +1851,7 @@ DESCRIPTION
|
||||
edit all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1873,7 +1873,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1909,7 +1909,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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:revert-update-rollout`
|
||||
|
||||
@@ -1936,7 +1936,7 @@ DESCRIPTION
|
||||
revert a rollout update for a project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1964,7 +1964,7 @@ 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/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1985,7 +1985,7 @@ DESCRIPTION
|
||||
instead execute "eas update:republish" or "eas update:roll-back-to-embedded".
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -2005,7 +2005,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas upload`
|
||||
|
||||
@@ -2026,7 +2026,7 @@ DESCRIPTION
|
||||
upload a local build and generate a sharable link
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/upload.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -2047,7 +2047,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -2067,7 +2067,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -2085,7 +2085,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -2107,7 +2107,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -2124,7 +2124,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v16.20.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/v16.22.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -2244,7 +2244,7 @@ DESCRIPTION
|
||||
to cancel.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
@@ -2252,19 +2252,19 @@ create a new workflow configuration YAML file
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:create [NAME] [--non-interactive]
|
||||
$ eas workflow:create [NAME] [--skip-validation]
|
||||
|
||||
ARGUMENTS
|
||||
NAME Name of the workflow file (must end with .yml or .yaml)
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--skip-validation If set, the workflow file will not be validated before being created
|
||||
|
||||
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/v16.20.3/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:logs [ID]`
|
||||
|
||||
@@ -2288,15 +2288,15 @@ DESCRIPTION
|
||||
If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
|
||||
## `eas workflow:run [FILE]`
|
||||
|
||||
run an EAS workflow
|
||||
run an EAS workflow. The entire local project directory will be packaged and uploaded to EAS servers for the workflow run, unless the --ref flag is used.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas workflow:run [FILE] [--non-interactive] [--wait] [-F <value>] [--json]
|
||||
$ eas workflow:run [FILE] [--non-interactive] [--wait] [-F <value>] [--ref <value>] [--json]
|
||||
|
||||
ARGUMENTS
|
||||
FILE Path to the workflow file to run
|
||||
@@ -2305,22 +2305,30 @@ FLAGS
|
||||
-F, --input=<value>... Set workflow inputs
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--ref=<value> Git reference to run the workflow on
|
||||
--[no-]wait Wait for workflow run to complete
|
||||
|
||||
DESCRIPTION
|
||||
run an EAS workflow
|
||||
run an EAS workflow. The entire local project directory will be packaged and uploaded to EAS servers for the workflow
|
||||
run, unless the --ref flag is used.
|
||||
|
||||
FLAG DESCRIPTIONS
|
||||
-F, --input=<value>... Set workflow inputs
|
||||
|
||||
Add a parameter in key=value format. Use multiple instances of this flag to set multiple inputs.
|
||||
|
||||
--ref=<value> Git reference to run the workflow on
|
||||
|
||||
The git reference must exist in the project's git repository, and the workflow file must exist at that reference.
|
||||
When this flag is used, the local project is not uploaded; instead, the workflow is run from the exact state of the
|
||||
project at the chosen reference.
|
||||
|
||||
--[no-]wait Wait for workflow run to complete
|
||||
|
||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:runs`
|
||||
|
||||
@@ -2345,7 +2353,7 @@ DESCRIPTION
|
||||
list recent workflow runs for this project, with their IDs, statuses, and timestamps
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -2365,7 +2373,7 @@ DESCRIPTION
|
||||
validate a workflow configuration yaml file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
|
||||
## `eas workflow:view [ID]`
|
||||
|
||||
@@ -2387,5 +2395,5 @@ DESCRIPTION
|
||||
workflow runs for the current project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v16.20.3/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v16.22.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+48
@@ -1470,6 +1470,30 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "environmentVariableEnvironments",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "EnvironmentVariableEnvironment",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "environmentVariables",
|
||||
"description": "Environment variables for an account",
|
||||
@@ -8983,6 +9007,30 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "environmentVariableEnvironments",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "EnvironmentVariableEnvironment",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "environmentVariables",
|
||||
"description": "Environment variables for an app",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "16.20.3",
|
||||
"version": "16.22.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
|
||||
@@ -17,7 +17,7 @@ const BuildProfileEnvironmentToEnvironment: Record<Environment, EnvironmentVaria
|
||||
|
||||
export function isEnvironment(env: string): env is EnvironmentVariableEnvironment {
|
||||
return Object.values(EnvironmentVariableEnvironment).includes(
|
||||
env as EnvironmentVariableEnvironment
|
||||
env.toLowerCase() as EnvironmentVariableEnvironment
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { BuildProfile, EasJsonAccessor, EasJsonUtils, Platform } from '@expo/eas-json';
|
||||
import { AndroidBuildProfile, IosBuildProfile } from '@expo/eas-json/build/build/types';
|
||||
|
||||
import BuildConfigure from '../../commands/build/configure';
|
||||
import UpdateConfigure from '../../commands/update/configure';
|
||||
import Log from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
|
||||
export async function buildProfileNamesFromProjectAsync(projectDir: string): Promise<Set<string>> {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
|
||||
const buildProfileNames = new Set(
|
||||
easJsonAccessor && (await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor))
|
||||
);
|
||||
return buildProfileNames;
|
||||
}
|
||||
export async function getBuildProfileAsync(
|
||||
projectDir: string,
|
||||
platform: Platform,
|
||||
profileName: string
|
||||
): Promise<BuildProfile<Platform>> {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const buildProfile = await EasJsonUtils.getBuildProfileAsync(
|
||||
easJsonAccessor,
|
||||
platform,
|
||||
profileName
|
||||
);
|
||||
return buildProfile;
|
||||
}
|
||||
export async function buildProfilesFromProjectAsync(
|
||||
projectDir: string
|
||||
): Promise<Map<string, { android: AndroidBuildProfile; ios: IosBuildProfile }>> {
|
||||
const buildProfileNames = await buildProfileNamesFromProjectAsync(projectDir);
|
||||
const buildProfiles: Map<string, { android: AndroidBuildProfile; ios: IosBuildProfile }> =
|
||||
new Map();
|
||||
for (const profileName of buildProfileNames) {
|
||||
const iosBuildProfile = (await getBuildProfileAsync(
|
||||
projectDir,
|
||||
Platform.IOS,
|
||||
profileName
|
||||
)) as IosBuildProfile;
|
||||
const androidBuildProfile = (await getBuildProfileAsync(
|
||||
projectDir,
|
||||
Platform.ANDROID,
|
||||
profileName
|
||||
)) as AndroidBuildProfile;
|
||||
buildProfiles.set(profileName, { android: androidBuildProfile, ios: iosBuildProfile });
|
||||
}
|
||||
return buildProfiles;
|
||||
}
|
||||
export function isBuildProfileForDevelopment(
|
||||
buildProfile: BuildProfile<Platform>,
|
||||
platform: Platform
|
||||
): boolean {
|
||||
if (buildProfile.developmentClient) {
|
||||
return true;
|
||||
}
|
||||
if (platform === Platform.ANDROID) {
|
||||
return (buildProfile as BuildProfile<Platform.ANDROID>).gradleCommand === ':app:assembleDebug';
|
||||
}
|
||||
if (platform === Platform.IOS) {
|
||||
return (buildProfile as BuildProfile<Platform.IOS>).buildConfiguration === 'Debug';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function isIosBuildProfileForSimulator(buildProfile: BuildProfile<Platform.IOS>): boolean {
|
||||
return buildProfile.simulator ?? false;
|
||||
}
|
||||
export async function addAndroidDevelopmentBuildProfileToEasJsonAsync(
|
||||
projectDir: string,
|
||||
buildProfileName: string
|
||||
): Promise<void> {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
easJsonRawObject.build = {
|
||||
...easJsonRawObject.build,
|
||||
[buildProfileName]: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
android: {
|
||||
gradleCommand: ':app:assembleDebug',
|
||||
},
|
||||
},
|
||||
};
|
||||
return easJsonRawObject;
|
||||
});
|
||||
await easJsonAccessor.writeAsync();
|
||||
}
|
||||
export async function addIosDevelopmentBuildProfileToEasJsonAsync(
|
||||
projectDir: string,
|
||||
buildProfileName: string,
|
||||
simulator: boolean
|
||||
): Promise<void> {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
easJsonRawObject.build = {
|
||||
...easJsonRawObject.build,
|
||||
[buildProfileName]: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
ios: {
|
||||
buildConfiguration: 'Debug',
|
||||
simulator,
|
||||
},
|
||||
},
|
||||
};
|
||||
return easJsonRawObject;
|
||||
});
|
||||
await easJsonAccessor.writeAsync();
|
||||
}
|
||||
|
||||
export async function addProductionBuildProfileToEasJsonIfNeededAsync(
|
||||
projectDir: string
|
||||
): Promise<boolean> {
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
await easJsonAccessor.readRawJsonAsync();
|
||||
let profileAdded = false;
|
||||
easJsonAccessor.patch(easJsonRawObject => {
|
||||
if (!easJsonRawObject.build?.production) {
|
||||
profileAdded = true;
|
||||
easJsonRawObject.build = {
|
||||
...(easJsonRawObject.build ?? {}),
|
||||
production: {},
|
||||
};
|
||||
// Also add the profile to submit
|
||||
easJsonRawObject.submit = {
|
||||
...(easJsonRawObject.submit ?? {}),
|
||||
production: {},
|
||||
};
|
||||
}
|
||||
return easJsonRawObject;
|
||||
});
|
||||
if (profileAdded) {
|
||||
Log.log('Added missing production build profile to eas.json');
|
||||
}
|
||||
await easJsonAccessor.writeAsync();
|
||||
return profileAdded;
|
||||
}
|
||||
|
||||
export async function hasBuildConfigureBeenRunAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
}: {
|
||||
projectDir: string;
|
||||
expoConfig: ExpoConfig;
|
||||
}): Promise<boolean> {
|
||||
// Is there a project ID in the Expo config?
|
||||
if (!expoConfig.extra?.eas?.projectId) {
|
||||
return false;
|
||||
}
|
||||
// Is there an eas.json?
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
try {
|
||||
await easJsonAccessor.readAsync();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function hasUpdateConfigureBeenRunAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
}: {
|
||||
projectDir: string;
|
||||
expoConfig: ExpoConfig;
|
||||
}): Promise<boolean> {
|
||||
// Does the Expo config have an updates URL?
|
||||
if (!expoConfig.updates?.url) {
|
||||
return false;
|
||||
}
|
||||
// Does at least one build profile have a channel?
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
try {
|
||||
const easJson = await easJsonAccessor.readAsync();
|
||||
return Object.values(easJson.build ?? {}).some(buildProfile => !!buildProfile.channel);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs update:configure if needed. Returns a boolean (proceed with workflow creation, or not)
|
||||
*/
|
||||
|
||||
export async function runUpdateConfigureIfNeededAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
}: {
|
||||
projectDir: string;
|
||||
expoConfig: ExpoConfig;
|
||||
}): Promise<boolean> {
|
||||
if (
|
||||
await hasUpdateConfigureBeenRunAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const nextStep = (
|
||||
await promptAsync({
|
||||
type: 'select',
|
||||
name: 'nextStep',
|
||||
message:
|
||||
'You have chosen to create a workflow that requires EAS Update configuration. What would you like to do?',
|
||||
choices: [
|
||||
{ title: 'Configure EAS Update and then proceed', value: 'configure' },
|
||||
{ title: 'EAS Update is already configured, proceed', value: 'proceed' },
|
||||
{ title: 'Choose a different workflow template', value: 'repeat' },
|
||||
],
|
||||
})
|
||||
).nextStep;
|
||||
switch (nextStep) {
|
||||
case 'configure':
|
||||
Log.newLine();
|
||||
await UpdateConfigure.run([]);
|
||||
return true;
|
||||
case 'proceed':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Runs build:configure if needed. Returns a boolean (proceed with workflow creation, or not)
|
||||
*/
|
||||
export async function runBuildConfigureIfNeededAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
}: {
|
||||
projectDir: string;
|
||||
expoConfig: ExpoConfig;
|
||||
}): Promise<boolean> {
|
||||
if (
|
||||
await hasBuildConfigureBeenRunAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const nextStep = (
|
||||
await promptAsync({
|
||||
type: 'select',
|
||||
name: 'nextStep',
|
||||
message:
|
||||
'You have chosen to create a workflow that requires EAS Build configuration. What would you like to do?',
|
||||
choices: [
|
||||
{ title: 'Configure EAS Build and then proceed', value: 'configure' },
|
||||
{ title: 'EAS Build is already configured, proceed', value: 'proceed' },
|
||||
{ title: 'Choose a different workflow template', value: 'repeat' },
|
||||
],
|
||||
})
|
||||
).nextStep;
|
||||
switch (nextStep) {
|
||||
case 'configure':
|
||||
Log.newLine();
|
||||
await BuildConfigure.run(['-p', 'all']);
|
||||
return true;
|
||||
case 'proceed':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import {
|
||||
addAndroidDevelopmentBuildProfileToEasJsonAsync,
|
||||
addIosDevelopmentBuildProfileToEasJsonAsync,
|
||||
addProductionBuildProfileToEasJsonIfNeededAsync,
|
||||
buildProfilesFromProjectAsync,
|
||||
hasBuildConfigureBeenRunAsync,
|
||||
hasUpdateConfigureBeenRunAsync,
|
||||
isBuildProfileForDevelopment,
|
||||
isIosBuildProfileForSimulator,
|
||||
} from './buildProfileUtils';
|
||||
import Log, { link } from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
|
||||
export enum WorkflowStarterName {
|
||||
BUILD = 'build',
|
||||
UPDATE = 'update',
|
||||
CUSTOM = 'custom',
|
||||
DEPLOY = 'deploy',
|
||||
}
|
||||
|
||||
export type WorkflowStarter = {
|
||||
name: WorkflowStarterName;
|
||||
displayName: string;
|
||||
defaultFileName: string;
|
||||
template: any;
|
||||
header: string;
|
||||
nextSteps?: string[];
|
||||
};
|
||||
|
||||
const createdByEASCLI = `# Created by EAS CLI v${easCliVersion}`;
|
||||
|
||||
const CUSTOM_TEMPLATE = {
|
||||
name: 'Custom workflow',
|
||||
on: {
|
||||
push: {
|
||||
branches: ['main'],
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
custom_build: {
|
||||
name: 'Custom job',
|
||||
steps: [
|
||||
{
|
||||
uses: 'eas/checkout',
|
||||
},
|
||||
{
|
||||
name: 'Hello World',
|
||||
id: 'hello_world',
|
||||
run: '# Custom script\necho "Hello, World"\n',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const CUSTOM_TEMPLATE_HEADER = `
|
||||
# Custom job
|
||||
#
|
||||
# This workflow shows how to write custom jobs.
|
||||
# It contains a predefined workflow step and a shell command to print "Hello, World!".
|
||||
#
|
||||
# Key features:
|
||||
# - Triggers on pushes to the main branch.
|
||||
# (Requires linked GitHub account: https://expo.dev/accounts/[account]/settings/github)
|
||||
# - Can be triggered manually with eas workflow:run custom.yml
|
||||
# - Runs eas/checkout then a custom "echo" command
|
||||
#
|
||||
# For detailed documentation on workflow syntax and available step types, visit:
|
||||
# https://docs.expo.dev/eas/workflows/syntax/#jobsjob_idstepsstepuses
|
||||
#
|
||||
${createdByEASCLI}
|
||||
#
|
||||
`;
|
||||
|
||||
const BUILD_TEMPLATE = {
|
||||
name: 'Create development builds',
|
||||
on: {
|
||||
push: {
|
||||
branches: ['main'],
|
||||
},
|
||||
},
|
||||
jobs: {},
|
||||
};
|
||||
|
||||
const BUILD_TEMPLATE_HEADER = `
|
||||
# Create development builds
|
||||
#
|
||||
# This workflow shows how to create development builds.
|
||||
#
|
||||
# Key features:
|
||||
# - Can be triggered manually with eas workflow:run create-development-builds.yml
|
||||
# - Runs the pre-packaged build job to create Android and iOS development builds
|
||||
# for Android emulators, Android and iOS devices, and iOS simulators
|
||||
#
|
||||
# For a detailed guide on using this workflow, visit:
|
||||
# https://docs.expo.dev/develop/development-builds/introduction/
|
||||
#
|
||||
${createdByEASCLI}
|
||||
#
|
||||
`;
|
||||
|
||||
const PUBLISH_UPDATE_TEMPLATE = {
|
||||
name: 'Publish preview update',
|
||||
on: {
|
||||
push: {
|
||||
branches: ['*'],
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
publish_preview_update: {
|
||||
name: 'Publish preview update',
|
||||
type: 'update',
|
||||
params: {
|
||||
branch: '${{ github.ref_name || "test" }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const PUBLISH_UPDATE_TEMPLATE_HEADER = `
|
||||
# Publish preview update
|
||||
#
|
||||
# This workflow shows how to publish preview updates.
|
||||
# Learn more: https://docs.expo.dev/review/share-previews-with-your-team/
|
||||
#
|
||||
# Key features:
|
||||
# - Triggers on pushes to all branches
|
||||
# (Requires linked GitHub account: https://expo.dev/accounts/[account]/settings/github)
|
||||
# - Can be triggered manually with eas workflow:run publish-preview-update.yml
|
||||
# - Runs the pre-packaged update job
|
||||
#
|
||||
# For a detailed guide on using this workflow, visit:
|
||||
# https://docs.expo.dev/eas/workflows/examples/publish-preview-update/
|
||||
#
|
||||
${createdByEASCLI}
|
||||
#
|
||||
`;
|
||||
|
||||
const DEPLOY_TEMPLATE = {
|
||||
name: 'Deploy to production',
|
||||
on: {
|
||||
push: {
|
||||
branches: ['main'],
|
||||
},
|
||||
},
|
||||
jobs: {
|
||||
fingerprint: {
|
||||
name: 'Fingerprint',
|
||||
type: 'fingerprint',
|
||||
},
|
||||
get_android_build: {
|
||||
name: 'Check for existing android build',
|
||||
needs: ['fingerprint'],
|
||||
type: 'get-build',
|
||||
params: {
|
||||
fingerprint_hash: '${{ needs.fingerprint.outputs.android_fingerprint_hash }}',
|
||||
profile: 'production',
|
||||
},
|
||||
},
|
||||
get_ios_build: {
|
||||
name: 'Check for existing ios build',
|
||||
needs: ['fingerprint'],
|
||||
type: 'get-build',
|
||||
params: {
|
||||
fingerprint_hash: '${{ needs.fingerprint.outputs.ios_fingerprint_hash }}',
|
||||
profile: 'production',
|
||||
},
|
||||
},
|
||||
build_android: {
|
||||
name: 'Build Android',
|
||||
needs: ['get_android_build'],
|
||||
if: '${{ !needs.get_android_build.outputs.build_id }}',
|
||||
type: 'build',
|
||||
params: {
|
||||
platform: 'android',
|
||||
profile: 'production',
|
||||
},
|
||||
},
|
||||
build_ios: {
|
||||
name: 'Build iOS',
|
||||
needs: ['get_ios_build'],
|
||||
if: '${{ !needs.get_ios_build.outputs.build_id }}',
|
||||
type: 'build',
|
||||
params: {
|
||||
platform: 'ios',
|
||||
profile: 'production',
|
||||
},
|
||||
},
|
||||
submit_android_build: {
|
||||
name: 'Submit Android Build',
|
||||
needs: ['build_android'],
|
||||
type: 'submit',
|
||||
params: {
|
||||
build_id: '${{ needs.build_android.outputs.build_id }}',
|
||||
},
|
||||
},
|
||||
submit_ios_build: {
|
||||
name: 'Submit iOS Build',
|
||||
needs: ['build_ios'],
|
||||
type: 'submit',
|
||||
params: {
|
||||
build_id: '${{ needs.build_ios.outputs.build_id }}',
|
||||
},
|
||||
},
|
||||
publish_android_update: {
|
||||
name: 'Publish Android update',
|
||||
needs: ['get_android_build'],
|
||||
if: '${{ needs.get_android_build.outputs.build_id }}',
|
||||
type: 'update',
|
||||
params: {
|
||||
branch: 'production',
|
||||
platform: 'android',
|
||||
},
|
||||
},
|
||||
publish_ios_update: {
|
||||
name: 'Publish iOS update',
|
||||
needs: ['get_ios_build'],
|
||||
if: '${{ needs.get_ios_build.outputs.build_id }}',
|
||||
type: 'update',
|
||||
params: {
|
||||
branch: 'production',
|
||||
platform: 'ios',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DEPLOY_TEMPLATE_HEADER = `
|
||||
# Deploy to production
|
||||
#
|
||||
# This workflow shows how to build and submit to app stores.
|
||||
#
|
||||
# Key features:
|
||||
# - Triggers on pushes to "main" (Requires linked GitHub account: https://expo.dev/accounts/[account]/settings/github)
|
||||
# - Can be triggered manually with eas workflow:run deploy-to-production.yml
|
||||
# - Creates builds and submits them to app stores when native changes are detected, otherwise sends an over-the-air update.
|
||||
#
|
||||
# For a detailed guide on using this workflow, visit:
|
||||
# https://docs.expo.dev/eas/workflows/examples/deploy-to-production/
|
||||
`;
|
||||
|
||||
function nextStepForDeviceBuildProfile(buildProfileName: string): string {
|
||||
return `A build job in this workflow uses the build profile "${buildProfileName}"
|
||||
to build your app for real devices, and may require credentials.
|
||||
You can configure your credentials with the command "eas credentials".
|
||||
For more information, please see ${link('https://docs.expo.dev/app-signing/app-credentials/')}.
|
||||
`
|
||||
.trim()
|
||||
.trimStart();
|
||||
}
|
||||
|
||||
function nextStepForAppSubmission(): string {
|
||||
return `This workflow includes a job to submit your app to app stores.
|
||||
You will need to configure your App Store/Play Store credentials
|
||||
before you can run this workflow.
|
||||
Please see ${link('https://docs.expo.dev/deploy/submit-to-app-stores/')}.
|
||||
`
|
||||
.trim()
|
||||
.trimStart();
|
||||
}
|
||||
|
||||
export function howToRunWorkflow(
|
||||
workflowFileName: string,
|
||||
workflowStarter: WorkflowStarter
|
||||
): string {
|
||||
const lines = [
|
||||
`To run this workflow manually, use the command "eas workflow:run .eas/workflows/${workflowFileName}".`,
|
||||
];
|
||||
if (workflowStarter.template?.on?.push?.branches?.length > 0) {
|
||||
const branches = workflowStarter.template.on.push.branches;
|
||||
if (branches.length === 1 && branches[0] === '*') {
|
||||
lines.push(
|
||||
'This workflow is also configured to run automatically when code is pushed to any branch.'
|
||||
);
|
||||
} else if (branches.length === 1) {
|
||||
lines.push(
|
||||
`This workflow is also configured to run automatically when code is pushed to the "${branches[0]}" branch.`
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
`This workflow is also configured to run automatically when code is pushed to the following branches: ${branches.join(
|
||||
', '
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export const workflowStarters: WorkflowStarter[] = [
|
||||
{
|
||||
displayName: 'Custom',
|
||||
name: WorkflowStarterName.CUSTOM,
|
||||
defaultFileName: 'custom.yml',
|
||||
template: CUSTOM_TEMPLATE,
|
||||
header: CUSTOM_TEMPLATE_HEADER,
|
||||
},
|
||||
{
|
||||
displayName: 'Create development builds',
|
||||
name: WorkflowStarterName.BUILD,
|
||||
defaultFileName: 'create-development-builds.yml',
|
||||
template: BUILD_TEMPLATE,
|
||||
header: BUILD_TEMPLATE_HEADER,
|
||||
},
|
||||
{
|
||||
displayName: 'Publish updates',
|
||||
name: WorkflowStarterName.UPDATE,
|
||||
defaultFileName: 'publish-updates.yml',
|
||||
template: PUBLISH_UPDATE_TEMPLATE,
|
||||
header: PUBLISH_UPDATE_TEMPLATE_HEADER,
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to production',
|
||||
name: WorkflowStarterName.DEPLOY,
|
||||
defaultFileName: 'deploy-to-production.yml',
|
||||
template: DEPLOY_TEMPLATE,
|
||||
header: DEPLOY_TEMPLATE_HEADER,
|
||||
},
|
||||
];
|
||||
|
||||
export async function addBuildJobsToDevelopmentBuildTemplateAsync(
|
||||
projectDir: string,
|
||||
workflowStarter: WorkflowStarter
|
||||
): Promise<WorkflowStarter> {
|
||||
const buildProfiles = await buildProfilesFromProjectAsync(projectDir);
|
||||
// android_development_build
|
||||
|
||||
const nextSteps: Set<string> = new Set();
|
||||
|
||||
let androidDevelopmentBuildProfileName: string | null = null;
|
||||
for (const profileName of buildProfiles.keys()) {
|
||||
const profile = buildProfiles.get(profileName)?.android;
|
||||
if (!profile) {
|
||||
continue;
|
||||
}
|
||||
if (isBuildProfileForDevelopment(profile, Platform.ANDROID)) {
|
||||
androidDevelopmentBuildProfileName = profileName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!androidDevelopmentBuildProfileName) {
|
||||
Log.warn(
|
||||
'This workflow requires an Android development build profile in your eas.json, but none were found.'
|
||||
);
|
||||
const add = await confirmAsync({
|
||||
message: 'Do you want to add an Android development build profile?',
|
||||
initial: false,
|
||||
});
|
||||
if (add) {
|
||||
let androidDevelopmentBuildProfileName = 'android_development';
|
||||
while (buildProfiles.has(androidDevelopmentBuildProfileName)) {
|
||||
androidDevelopmentBuildProfileName = `${androidDevelopmentBuildProfileName}_1`;
|
||||
}
|
||||
await addAndroidDevelopmentBuildProfileToEasJsonAsync(
|
||||
projectDir,
|
||||
androidDevelopmentBuildProfileName
|
||||
);
|
||||
} else {
|
||||
Log.log('Skipping Android development build job...');
|
||||
}
|
||||
}
|
||||
if (androidDevelopmentBuildProfileName) {
|
||||
Log.log(`Using Android development build profile: ${androidDevelopmentBuildProfileName}`);
|
||||
workflowStarter.template.jobs.android_development_build = {
|
||||
name: `Build ${androidDevelopmentBuildProfileName} for android`,
|
||||
type: 'build',
|
||||
params: {
|
||||
profile: androidDevelopmentBuildProfileName,
|
||||
platform: 'android',
|
||||
},
|
||||
};
|
||||
nextSteps.add(nextStepForDeviceBuildProfile(androidDevelopmentBuildProfileName));
|
||||
}
|
||||
|
||||
// ios_simulator_development_build
|
||||
let iosSimulatorDevelopmentBuildProfileName: string | null = null;
|
||||
for (const profileName of buildProfiles.keys()) {
|
||||
const profile = buildProfiles.get(profileName)?.ios;
|
||||
if (!profile) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
isBuildProfileForDevelopment(profile, Platform.IOS) &&
|
||||
isIosBuildProfileForSimulator(profile)
|
||||
) {
|
||||
iosSimulatorDevelopmentBuildProfileName = profileName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!iosSimulatorDevelopmentBuildProfileName) {
|
||||
Log.warn(
|
||||
'This workflow requires an iOS simulator development build profile in your eas.json, but none were found.'
|
||||
);
|
||||
const add = await confirmAsync({
|
||||
message: 'Do you want to add an iOS simulator development build profile?',
|
||||
initial: false,
|
||||
});
|
||||
if (add) {
|
||||
let iosSimulatorDevelopmentBuildProfileName = 'ios_simulator_development';
|
||||
while (buildProfiles.has(iosSimulatorDevelopmentBuildProfileName)) {
|
||||
iosSimulatorDevelopmentBuildProfileName = `${iosSimulatorDevelopmentBuildProfileName}_1`;
|
||||
}
|
||||
await addIosDevelopmentBuildProfileToEasJsonAsync(
|
||||
projectDir,
|
||||
iosSimulatorDevelopmentBuildProfileName,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
Log.log('Skipping iOS simulator development build job...');
|
||||
}
|
||||
}
|
||||
if (iosSimulatorDevelopmentBuildProfileName) {
|
||||
Log.log(
|
||||
`Using iOS simulator development build profile: ${iosSimulatorDevelopmentBuildProfileName}`
|
||||
);
|
||||
workflowStarter.template.jobs.ios_simulator_development_build = {
|
||||
name: `Build ${iosSimulatorDevelopmentBuildProfileName} for iOS simulator`,
|
||||
type: 'build',
|
||||
params: {
|
||||
profile: iosSimulatorDevelopmentBuildProfileName,
|
||||
platform: 'ios',
|
||||
},
|
||||
};
|
||||
}
|
||||
// ios_device_development_build
|
||||
let iosDeviceDevelopmentBuildProfileName: string | null = null;
|
||||
for (const profileName of buildProfiles.keys()) {
|
||||
const profile = buildProfiles.get(profileName)?.ios;
|
||||
if (!profile) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
isBuildProfileForDevelopment(profile, Platform.IOS) &&
|
||||
!isIosBuildProfileForSimulator(profile)
|
||||
) {
|
||||
iosDeviceDevelopmentBuildProfileName = profileName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!iosDeviceDevelopmentBuildProfileName) {
|
||||
Log.warn(
|
||||
'This workflow requires an iOS device development build profile in your eas.json, but none were found.'
|
||||
);
|
||||
const add = await confirmAsync({
|
||||
message: 'Do you want to add an iOS device development build profile?',
|
||||
initial: false,
|
||||
});
|
||||
if (add) {
|
||||
let iosDeviceDevelopmentBuildProfileName = 'ios_device_development';
|
||||
while (buildProfiles.has(iosDeviceDevelopmentBuildProfileName)) {
|
||||
iosDeviceDevelopmentBuildProfileName = `${iosDeviceDevelopmentBuildProfileName}_1`;
|
||||
}
|
||||
await addIosDevelopmentBuildProfileToEasJsonAsync(
|
||||
projectDir,
|
||||
iosDeviceDevelopmentBuildProfileName,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
Log.log('Skipping iOS device development build job...');
|
||||
}
|
||||
}
|
||||
if (iosDeviceDevelopmentBuildProfileName) {
|
||||
Log.log(`Using iOS device development build profile: ${iosDeviceDevelopmentBuildProfileName}`);
|
||||
workflowStarter.template.jobs.ios_development_build = {
|
||||
name: `Build ${iosDeviceDevelopmentBuildProfileName} for iOS simulator`,
|
||||
type: 'build',
|
||||
params: {
|
||||
profile: iosDeviceDevelopmentBuildProfileName,
|
||||
platform: 'ios',
|
||||
},
|
||||
};
|
||||
nextSteps.add(nextStepForDeviceBuildProfile(iosDeviceDevelopmentBuildProfileName));
|
||||
}
|
||||
workflowStarter.nextSteps = [...nextSteps];
|
||||
return workflowStarter;
|
||||
}
|
||||
|
||||
export async function ensureProductionBuildProfileExistsAsync(
|
||||
projectDir: string,
|
||||
workflowStarter: WorkflowStarter
|
||||
): Promise<WorkflowStarter> {
|
||||
await addProductionBuildProfileToEasJsonIfNeededAsync(projectDir);
|
||||
workflowStarter.nextSteps = [
|
||||
nextStepForDeviceBuildProfile('production'),
|
||||
nextStepForAppSubmission(),
|
||||
];
|
||||
return workflowStarter;
|
||||
}
|
||||
|
||||
export async function customizeTemplateIfNeededAsync(
|
||||
workflowStarter: WorkflowStarter,
|
||||
projectDir: string,
|
||||
expoConfig: ExpoConfig
|
||||
): Promise<any> {
|
||||
// Ensure EAS Build is configured
|
||||
switch (workflowStarter.name) {
|
||||
case WorkflowStarterName.BUILD:
|
||||
case WorkflowStarterName.DEPLOY:
|
||||
case WorkflowStarterName.UPDATE:
|
||||
if (!(await hasBuildConfigureBeenRunAsync({ projectDir, expoConfig }))) {
|
||||
throw new Error(
|
||||
'EAS Build is not configured for this project. Please run "eas build:configure" to configure it.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Ensure EAS Update is configured
|
||||
switch (workflowStarter.name) {
|
||||
case WorkflowStarterName.DEPLOY:
|
||||
case WorkflowStarterName.UPDATE:
|
||||
if (!(await hasUpdateConfigureBeenRunAsync({ projectDir, expoConfig }))) {
|
||||
throw new Error(
|
||||
'EAS Update is not configured for this project. Please run "eas update:configure" to configure it.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Customize template
|
||||
switch (workflowStarter.name) {
|
||||
case WorkflowStarterName.BUILD:
|
||||
Log.debug('Adding build jobs to template...');
|
||||
return await addBuildJobsToDevelopmentBuildTemplateAsync(projectDir, workflowStarter);
|
||||
case WorkflowStarterName.DEPLOY:
|
||||
return await ensureProductionBuildProfileExistsAsync(projectDir, workflowStarter);
|
||||
default:
|
||||
return workflowStarter;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
|
||||
import { InvalidEasJsonError, MissingEasJsonError } from '@expo/eas-json/build/errors';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import * as YAML from 'yaml';
|
||||
|
||||
import { buildProfileNamesFromProjectAsync } from './buildProfileUtils';
|
||||
import { getExpoApiWorkflowSchemaURL } from '../../api';
|
||||
import { WorkflowRevisionMutation } from '../../graphql/mutations/WorkflowRevisionMutation';
|
||||
import Log from '../../log';
|
||||
@@ -102,11 +102,8 @@ async function validateWorkflowBuildJobsAsync(parsedYaml: any, projectDir: strin
|
||||
if (buildJobs.length === 0) {
|
||||
return;
|
||||
}
|
||||
const easJsonAccessor = EasJsonAccessor.fromProjectPath(projectDir);
|
||||
const buildProfileNames = await buildProfileNamesFromProjectAsync(projectDir);
|
||||
|
||||
const buildProfileNames = new Set(
|
||||
easJsonAccessor && (await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor))
|
||||
);
|
||||
const invalidBuildJobs = buildJobs.filter(
|
||||
job =>
|
||||
!buildProfileNames.has(job.value.params.profile) &&
|
||||
@@ -165,13 +162,15 @@ function validateWorkflowStructure(parsedYaml: any, workflowJsonSchema: any): vo
|
||||
}
|
||||
}
|
||||
|
||||
function parsedYamlFromWorkflowContents(workflowFileContents: {
|
||||
yamlConfig: string;
|
||||
}): Promise<any> {
|
||||
export function parsedYamlFromWorkflowContents(workflowFileContents: { yamlConfig: string }): any {
|
||||
const parsedYaml = YAML.parse(workflowFileContents.yamlConfig);
|
||||
return parsedYaml;
|
||||
}
|
||||
|
||||
export function workflowContentsFromParsedYaml(parsedYaml: any): string {
|
||||
return YAML.stringify(parsedYaml);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
async function fetchWorkflowSchemaAsync(): Promise<any> {
|
||||
// EXPO_TESTING_WORKFLOW_SCHEMA_PATH is used only for testing against a different schema
|
||||
|
||||
@@ -138,6 +138,7 @@ export default class WorkerDeploy extends EasCommand {
|
||||
logExportedProjectInfo(projectDist);
|
||||
|
||||
async function* emitWorkerTarballAsync(params: {
|
||||
routesConfig: WorkerAssets.RoutesConfigEntry | null;
|
||||
assetMap: WorkerAssets.AssetMap;
|
||||
manifest: WorkerAssets.Manifest;
|
||||
}): AsyncGenerator<WorkerAssets.FileEntry> {
|
||||
@@ -148,6 +149,8 @@ export default class WorkerDeploy extends EasCommand {
|
||||
for await (const workerFile of workerFiles) {
|
||||
yield [`server/${workerFile.normalizedPath}`, workerFile.data];
|
||||
}
|
||||
} else if (projectDist.type === 'static' && params.routesConfig) {
|
||||
yield ['routes.json', JSON.stringify(params.routesConfig)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,12 +287,13 @@ export default class WorkerDeploy extends EasCommand {
|
||||
manifestResult.conflictingVariableNames.join(' ')
|
||||
);
|
||||
}
|
||||
assetFiles = await WorkerAssets.collectAssetsAsync(
|
||||
projectDist.type === 'server' ? projectDist.clientPath : projectDist.path,
|
||||
{ maxFileSize: MAX_UPLOAD_SIZE }
|
||||
);
|
||||
const assetPath = projectDist.type === 'server' ? projectDist.clientPath : projectDist.path;
|
||||
assetFiles = await WorkerAssets.collectAssetsAsync(assetPath, {
|
||||
maxFileSize: MAX_UPLOAD_SIZE,
|
||||
});
|
||||
tarPath = await WorkerAssets.packFilesIterableAsync(
|
||||
emitWorkerTarballAsync({
|
||||
routesConfig: await WorkerAssets.getRoutesConfigAsync(assetPath),
|
||||
assetMap: WorkerAssets.assetsToAssetsMap(assetFiles),
|
||||
manifest: manifestResult.manifest,
|
||||
})
|
||||
|
||||
@@ -369,4 +369,42 @@ describe(EnvCreate, () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
const command = new EnvCreate(
|
||||
[
|
||||
'development',
|
||||
'--name',
|
||||
'TEST_VAR',
|
||||
'--value',
|
||||
'test-value',
|
||||
'--visibility',
|
||||
'plaintext',
|
||||
'--scope',
|
||||
'project',
|
||||
'--non-interactive',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariableMutation.createForAppAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{
|
||||
name: 'TEST_VAR',
|
||||
value: 'test-value',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
testProjectId
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Config } from '@oclif/core';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../../build/utils/environment';
|
||||
import { EnvironmentVariableScope } from '../../../graphql/generated';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
@@ -119,4 +123,40 @@ describe(EnvDelete, () => {
|
||||
|
||||
expect(EnvironmentVariableMutation.deleteAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
const mockVariable = {
|
||||
id: 'var1',
|
||||
name: 'TEST_VAR_1',
|
||||
value: 'value1',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce([mockVariable]);
|
||||
jest.mocked(EnvironmentVariableMutation.deleteAsync).mockResolvedValueOnce({ id: 'var1' });
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
const command = new EnvDelete(
|
||||
['development', '--variable-name', 'TEST_VAR_1', '--scope', 'project', '--non-interactive'],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Config } from '@oclif/core';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../../build/utils/environment';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../../graphql/generated';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import EnvExec from '../exec';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvExec, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
|
||||
const mockEnvironmentVariables = [
|
||||
{
|
||||
id: 'var1',
|
||||
name: 'EXPO_PUBLIC_API_URL',
|
||||
value: 'https://api.example.com',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
// Mock GraphQL query
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
.mockResolvedValue(mockEnvironmentVariables);
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
const command = new EnvExec(['development', 'echo $EXPO_PUBLIC_API_URL'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: testProjectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects invalid environment when using positional argument', async () => {
|
||||
const command = new EnvExec(['invalid', 'echo test'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Invalid environment. Use one of 'production', 'preview', or 'development'."
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -111,4 +111,34 @@ describe(EnvGet, () => {
|
||||
expect(promptVariableEnvironmentAsync).toHaveBeenCalled();
|
||||
expect(promptVariableNameAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
.mockResolvedValueOnce(mockVariables);
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
const command = new EnvGet(
|
||||
['development', '--variable-name', 'TEST_VAR_1', '--scope', 'project'],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: testProjectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
filterNames: ['TEST_VAR_1'],
|
||||
includeFileContent: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -95,6 +95,26 @@ describe(EnvList, () => {
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);
|
||||
|
||||
const command = new EnvList(['development'], mockConfig); // Using positional argument
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: testProjectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
includeFileContent: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('lists project environment variables including sensitive values', async () => {
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
import { Config } from '@oclif/core';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../../build/utils/environment';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../../graphql/generated';
|
||||
import {
|
||||
EnvironmentVariableWithFileContent,
|
||||
EnvironmentVariablesQuery,
|
||||
} from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import EnvPull from '../pull';
|
||||
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvPull, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const testProjectDir = '/test/project';
|
||||
const testTargetPath = '.env.local';
|
||||
|
||||
const mockEnvironmentVariables: EnvironmentVariableWithFileContent[] = [
|
||||
{
|
||||
id: 'var1',
|
||||
name: 'EXPO_PUBLIC_API_URL',
|
||||
value: 'https://api.example.com',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
{
|
||||
id: 'var2',
|
||||
name: 'DATABASE_URL',
|
||||
value: 'postgres://localhost:5432/mydb',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Sensitive,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
{
|
||||
id: 'var3',
|
||||
name: 'SECRET_KEY',
|
||||
value: 'super-secret-key',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Secret,
|
||||
type: EnvironmentSecretType.String,
|
||||
},
|
||||
{
|
||||
id: 'var4',
|
||||
name: 'CONFIG_FILE',
|
||||
value: 'base64-encoded-file-content',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.FileBase64,
|
||||
valueWithFileContent: Buffer.from('{"key": "value"}').toString('base64'),
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'addNewLineIfNone').mockImplementation(() => {});
|
||||
|
||||
// Mock fs-extra methods
|
||||
jest.mocked(fs.exists).mockImplementation(() => Promise.resolve(false));
|
||||
jest.mocked(fs.writeFile).mockImplementation(() => Promise.resolve());
|
||||
jest.mocked(fs.mkdir).mockImplementation(() => Promise.resolve());
|
||||
jest.mocked(fs.readFile).mockImplementation(() => Promise.resolve(''));
|
||||
|
||||
// Mock GraphQL query
|
||||
jest
|
||||
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
|
||||
.mockResolvedValue(mockEnvironmentVariables);
|
||||
});
|
||||
|
||||
describe('environment validation', () => {
|
||||
it('accepts development environment (case insensitive)', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: testProjectId,
|
||||
environment: 'development',
|
||||
includeFileContent: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects invalid environment', async () => {
|
||||
const command = new EnvPull(['invalid'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Invalid environment. Use one of 'production', 'preview', or 'development'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('file operations', () => {
|
||||
it('writes environment variables to .env.local file', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
testTargetPath,
|
||||
expect.stringContaining('# Environment: development')
|
||||
);
|
||||
});
|
||||
|
||||
it('writes correct environment variable content', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
// Find the call that writes to .env.local (not the file variable)
|
||||
const writeFileCalls = jest.mocked(fs.writeFile).mock.calls;
|
||||
const envFileCall = writeFileCalls.find(call => call[0] === testTargetPath);
|
||||
expect(envFileCall).toBeDefined();
|
||||
const fileContent = envFileCall![1] as string;
|
||||
|
||||
// Check that the file contains the expected content
|
||||
expect(fileContent).toContain('# Environment: development');
|
||||
expect(fileContent).toContain('EXPO_PUBLIC_API_URL=https://api.example.com');
|
||||
expect(fileContent).toContain('DATABASE_URL=postgres://localhost:5432/mydb');
|
||||
expect(fileContent).toContain('# SECRET_KEY=***** (secret)');
|
||||
});
|
||||
|
||||
it('handles file variables correctly', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
// Should create .eas/.env directory for file variables
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(path.join(testProjectDir, '.eas', '.env'), {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
// Should write the file content (base64 encoded)
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
path.join(testProjectDir, '.eas', '.env', 'CONFIG_FILE'),
|
||||
Buffer.from('{"key": "value"}').toString('base64'),
|
||||
'base64'
|
||||
);
|
||||
|
||||
// Should include file path in env file
|
||||
const writeFileCalls = jest.mocked(fs.writeFile).mock.calls;
|
||||
const envFileCall = writeFileCalls.find(call => call[0] === testTargetPath);
|
||||
expect(envFileCall).toBeDefined();
|
||||
const fileContent = envFileCall![1] as string;
|
||||
expect(fileContent).toContain('CONFIG_FILE=');
|
||||
expect(fileContent).toContain(path.join(testProjectDir, '.eas', '.env', 'CONFIG_FILE'));
|
||||
});
|
||||
|
||||
it('prompts for overwrite when file exists in interactive mode', async () => {
|
||||
jest.mocked(fs.exists).mockImplementation(() => Promise.resolve(true));
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: `File ${testTargetPath} already exists. Do you want to overwrite it?`,
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts when user declines overwrite', async () => {
|
||||
jest.mocked(fs.exists).mockImplementation(() => Promise.resolve(true));
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(`File ${testTargetPath} already exists.`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-interactive mode', () => {
|
||||
it('works in non-interactive mode', async () => {
|
||||
const command = new EnvPull(['development', '--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: testProjectId,
|
||||
environment: 'development',
|
||||
includeFileContent: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('requires environment in non-interactive mode', async () => {
|
||||
const command = new EnvPull(['--non-interactive'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
'The `--environment` flag must be set when running in `--non-interactive` mode.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom target path', () => {
|
||||
it('writes to custom path when specified', async () => {
|
||||
const customPath = '.env.custom';
|
||||
const command = new EnvPull(['development', '--path', customPath], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
customPath,
|
||||
expect.stringContaining('# Environment: development')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logging', () => {
|
||||
it('logs success message with correct environment', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith(
|
||||
`Pulled plain text and sensitive environment variables from "development" environment to ${testTargetPath}.`
|
||||
);
|
||||
});
|
||||
|
||||
it('logs secret variables that were skipped', async () => {
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('The following variables have the secret visibility')
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('SECRET_KEY'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('existing .env.local file handling', () => {
|
||||
it('preserves existing secret values when they exist', async () => {
|
||||
const existingEnvContent = 'SECRET_KEY=existing-secret-value\nOTHER_VAR=other-value';
|
||||
jest.mocked(fs.exists).mockImplementation(() => Promise.resolve(true));
|
||||
jest.mocked(fs.readFile).mockImplementation(() => Promise.resolve(existingEnvContent));
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
|
||||
const command = new EnvPull(['development'], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
// Find the call that writes to .env.local (not the file variable)
|
||||
const writeFileCalls = jest.mocked(fs.writeFile).mock.calls;
|
||||
const envFileCall = writeFileCalls.find(call => call[0] === testTargetPath);
|
||||
expect(envFileCall).toBeDefined();
|
||||
const fileContent = envFileCall![1] as string;
|
||||
|
||||
// Should use existing secret value instead of placeholder
|
||||
expect(fileContent).toContain('SECRET_KEY=existing-secret-value');
|
||||
expect(fileContent).not.toContain('# SECRET_KEY=***** (secret)');
|
||||
|
||||
// Should log that it reused the local value
|
||||
expect(Log.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Reused local values for following secrets: SECRET_KEY')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Config } from '@oclif/core';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import { EnvironmentVariableEnvironment } from '../../../build/utils/environment';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../../graphql/generated';
|
||||
import { EnvironmentVariableMutation } from '../../../graphql/mutations/EnvironmentVariableMutation';
|
||||
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
|
||||
import Log from '../../../log';
|
||||
import EnvPush from '../push';
|
||||
|
||||
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
|
||||
jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('../../../log');
|
||||
|
||||
describe(EnvPush, () => {
|
||||
const graphqlClient = {} as any as ExpoGraphqlClient;
|
||||
const mockConfig = {} as unknown as Config;
|
||||
const testProjectDir = '/test/project';
|
||||
const testEnvPath = '.env.test';
|
||||
|
||||
const mockEnvContent = `EXPO_PUBLIC_API_URL=https://api.example.com
|
||||
DATABASE_URL=postgres://localhost:5432/mydb
|
||||
SECRET_KEY=super-secret-key`;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
// Mock fs-extra methods
|
||||
jest.mocked(fs.exists).mockImplementation(() => Promise.resolve(true));
|
||||
jest.mocked(fs.readFile).mockImplementation(() => Promise.resolve(mockEnvContent));
|
||||
|
||||
// Mock GraphQL queries and mutations
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValue([]); // No existing variables
|
||||
jest.mocked(EnvironmentVariableMutation.createForAppAsync).mockResolvedValue({
|
||||
id: 'var1',
|
||||
name: 'EXPO_PUBLIC_API_URL',
|
||||
value: 'https://api.example.com',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce([]);
|
||||
|
||||
const command = new EnvPush(['development', '--path', testEnvPath], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: testProjectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
filterNames: ['EXPO_PUBLIC_API_URL', 'DATABASE_URL', 'SECRET_KEY'],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid environment when using positional argument', async () => {
|
||||
const command = new EnvPush(['invalid', '--path', testEnvPath], mockConfig);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId: testProjectId,
|
||||
projectDir: testProjectDir,
|
||||
});
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(
|
||||
"Invalid environment. Use one of 'production', 'preview', or 'development'."
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
||||
import { getMockAppFragment } from '../../../__tests__/commands/utils';
|
||||
import { EnvironmentVariableEnvironment } from '../../../build/utils/environment';
|
||||
import {
|
||||
EnvironmentSecretType,
|
||||
EnvironmentVariableScope,
|
||||
EnvironmentVariableVisibility,
|
||||
} from '../../../graphql/generated';
|
||||
@@ -162,4 +163,50 @@ describe(EnvUpdate, () => {
|
||||
'Variable with name NON_EXISTENT_VARIABLE does not exist on project @testuser/testpp.'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts development environment when using positional argument', async () => {
|
||||
const mockVariable = {
|
||||
id: 'var1',
|
||||
name: 'TEST_VAR_1',
|
||||
value: 'value1',
|
||||
environments: [EnvironmentVariableEnvironment.Development],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
scope: EnvironmentVariableScope.Project,
|
||||
visibility: EnvironmentVariableVisibility.Public,
|
||||
type: EnvironmentSecretType.String,
|
||||
};
|
||||
|
||||
jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce([mockVariable]);
|
||||
jest.mocked(EnvironmentVariableMutation.updateAsync).mockResolvedValueOnce(mockVariable);
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
|
||||
const command = new EnvUpdate(
|
||||
[
|
||||
'development',
|
||||
'--variable-name',
|
||||
'TEST_VAR_1',
|
||||
'--value',
|
||||
'new-value',
|
||||
'--scope',
|
||||
'project',
|
||||
'--non-interactive',
|
||||
],
|
||||
mockConfig
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
jest.spyOn(command, 'getContextAsync').mockReturnValue({
|
||||
loggedIn: { graphqlClient },
|
||||
projectId,
|
||||
});
|
||||
|
||||
await command.runAsync();
|
||||
|
||||
expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: projectId,
|
||||
environment: EnvironmentVariableEnvironment.Development,
|
||||
filterNames: ['TEST_VAR_1'],
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -101,7 +101,7 @@ export default class EnvExec extends EasCommand {
|
||||
);
|
||||
}
|
||||
|
||||
environment = environment?.toUpperCase();
|
||||
environment = environment?.toLowerCase();
|
||||
|
||||
if (!isEnvironment(environment)) {
|
||||
throw new Error("Invalid environment. Use one of 'production', 'preview', or 'development'.");
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ export default class EnvPull extends EasCommand {
|
||||
flags: { environment: flagEnvironment, path: targetPath, 'non-interactive': nonInteractive },
|
||||
} = await this.parse(EnvPull);
|
||||
|
||||
let environment = flagEnvironment?.toUpperCase() ?? argEnvironment?.toUpperCase();
|
||||
let environment = flagEnvironment?.toLowerCase() ?? argEnvironment?.toLowerCase();
|
||||
|
||||
if (!environment) {
|
||||
environment = await promptVariableEnvironmentAsync({ nonInteractive });
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs/promises';
|
||||
import fsExtra from 'fs-extra';
|
||||
import path from 'path';
|
||||
import prompts from 'prompts';
|
||||
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
runBuildConfigureIfNeededAsync,
|
||||
runUpdateConfigureIfNeededAsync,
|
||||
} from '../../commandUtils/workflow/buildProfileUtils';
|
||||
import {
|
||||
WorkflowStarter,
|
||||
WorkflowStarterName,
|
||||
customizeTemplateIfNeededAsync,
|
||||
howToRunWorkflow,
|
||||
workflowStarters,
|
||||
} from '../../commandUtils/workflow/creation';
|
||||
import {
|
||||
logWorkflowValidationErrors,
|
||||
validateWorkflowFileAsync,
|
||||
workflowContentsFromParsedYaml,
|
||||
} from '../../commandUtils/workflow/validation';
|
||||
import Log from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { WorkflowFile } from '../../utils/workflowFile';
|
||||
|
||||
const DEFAULT_WORKFLOW_NAME = 'workflow.yml';
|
||||
const HELLO_WORLD_TEMPLATE = `name: Hello World
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
|
||||
jobs:
|
||||
hello_world:
|
||||
steps:
|
||||
- uses: eas/checkout
|
||||
- run: echo "Hello, World"
|
||||
`;
|
||||
|
||||
export class WorkflowCreate extends EasCommand {
|
||||
static override description = 'create a new workflow configuration YAML file';
|
||||
|
||||
@@ -35,11 +38,16 @@ export class WorkflowCreate extends EasCommand {
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
'skip-validation': Flags.boolean({
|
||||
description: 'If set, the workflow file will not be validated before being created',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.DynamicProjectConfig,
|
||||
...this.ContextOptions.ProjectDir,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
@@ -48,74 +56,175 @@ export class WorkflowCreate extends EasCommand {
|
||||
flags,
|
||||
} = await this.parse(WorkflowCreate);
|
||||
|
||||
const { projectDir } = await this.getContextAsync(WorkflowCreate, {
|
||||
nonInteractive: flags['non-interactive'],
|
||||
});
|
||||
try {
|
||||
const {
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
loggedIn: { graphqlClient },
|
||||
projectDir,
|
||||
} = await this.getContextAsync(WorkflowCreate, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
let fileName = argFileName;
|
||||
const { exp: originalExpoConfig, projectId } = await getDynamicPrivateProjectConfigAsync();
|
||||
let expoConfig = originalExpoConfig;
|
||||
|
||||
if (!fileName) {
|
||||
const response = await prompts({
|
||||
let workflowStarter;
|
||||
while (!workflowStarter) {
|
||||
workflowStarter = await chooseTemplateAsync();
|
||||
switch (workflowStarter.name) {
|
||||
case WorkflowStarterName.BUILD:
|
||||
case WorkflowStarterName.DEPLOY:
|
||||
case WorkflowStarterName.UPDATE: {
|
||||
const shouldProceed = await runBuildConfigureIfNeededAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
});
|
||||
if (!shouldProceed) {
|
||||
workflowStarter = undefined;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (workflowStarter.name) {
|
||||
case WorkflowStarterName.DEPLOY:
|
||||
case WorkflowStarterName.UPDATE: {
|
||||
const shouldProceed = await runUpdateConfigureIfNeededAsync({
|
||||
projectDir,
|
||||
expoConfig,
|
||||
});
|
||||
if (!shouldProceed) {
|
||||
workflowStarter = undefined;
|
||||
continue;
|
||||
}
|
||||
// Need to refetch the Expo config because it may have changed
|
||||
expoConfig = (await getDynamicPrivateProjectConfigAsync()).exp;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const { fileName, filePath } = await chooseFileNameAsync(
|
||||
argFileName,
|
||||
projectDir,
|
||||
workflowStarter
|
||||
);
|
||||
|
||||
// Customize the template if needed
|
||||
workflowStarter = await customizeTemplateIfNeededAsync(
|
||||
workflowStarter,
|
||||
projectDir,
|
||||
expoConfig
|
||||
);
|
||||
|
||||
Log.debug(`Creating workflow file ${fileName} from template ${workflowStarter.name}`);
|
||||
const yamlString = [
|
||||
workflowStarter.header,
|
||||
workflowContentsFromParsedYaml(workflowStarter.template),
|
||||
].join('\n');
|
||||
|
||||
if (!flags['skip-validation']) {
|
||||
await validateWorkflowFileAsync(
|
||||
{ yamlConfig: yamlString, filePath: fileName },
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
projectId
|
||||
);
|
||||
}
|
||||
await ensureWorkflowsDirectoryExistsAsync({ projectDir });
|
||||
filePath && (await fs.writeFile(filePath, yamlString));
|
||||
Log.withTick(`Created ${chalk.bold(filePath)}`);
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(howToRunWorkflow(fileName, workflowStarter));
|
||||
|
||||
// Next steps
|
||||
if (workflowStarter.nextSteps && workflowStarter.nextSteps.length > 0) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log('Next steps:');
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
formatFields(
|
||||
workflowStarter.nextSteps.map((step: string, index: number) => ({
|
||||
label: `${index + 1}.`,
|
||||
value: step,
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logWorkflowValidationErrors(error);
|
||||
Log.error('Failed to create workflow file.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureWorkflowsDirectoryExistsAsync({
|
||||
projectDir,
|
||||
}: {
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await fs.access(path.join(projectDir, '.eas', 'workflows'));
|
||||
} catch {
|
||||
await fs.mkdir(path.join(projectDir, '.eas', 'workflows'), { recursive: true });
|
||||
Log.withTick(`Created directory ${chalk.bold(path.join(projectDir, '.eas', 'workflows'))}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function chooseTemplateAsync(): Promise<WorkflowStarter> {
|
||||
const workflowStarter = (
|
||||
await promptAsync({
|
||||
type: 'select',
|
||||
name: 'starter',
|
||||
message: 'Select a workflow template:',
|
||||
choices: workflowStarters.map(starter => ({
|
||||
title: starter.displayName,
|
||||
value: starter,
|
||||
})),
|
||||
})
|
||||
).starter;
|
||||
return workflowStarter;
|
||||
}
|
||||
|
||||
async function chooseFileNameAsync(
|
||||
initialValue: string | undefined,
|
||||
projectDir: string,
|
||||
workflowStarter: WorkflowStarter
|
||||
): Promise<{ fileName: string; filePath: string }> {
|
||||
let fileName = initialValue;
|
||||
let filePath = '';
|
||||
while ((fileName?.length ?? 0) === 0) {
|
||||
fileName = (
|
||||
await promptAsync({
|
||||
type: 'text',
|
||||
name: 'fileName',
|
||||
message: 'What would you like to name your workflow file?',
|
||||
initial: DEFAULT_WORKFLOW_NAME,
|
||||
validate: value => {
|
||||
try {
|
||||
WorkflowFile.validateYamlExtension(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error instanceof Error ? error.message : 'Invalid file name';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.fileName) {
|
||||
Log.warn('Workflow creation cancelled.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
fileName = response.fileName;
|
||||
initial: workflowStarter.defaultFileName,
|
||||
})
|
||||
).fileName;
|
||||
if (!fileName) {
|
||||
fileName = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensureWorkflowsDirectoryExistsAsync({ projectDir });
|
||||
await this.createWorkflowFileAsync({ fileName, projectDir });
|
||||
WorkflowFile.validateYamlExtension(fileName);
|
||||
} catch (error) {
|
||||
Log.error('Failed to create workflow file.');
|
||||
throw error;
|
||||
Log.error(error instanceof Error ? error.message : 'Invalid YAML file name extension');
|
||||
fileName = undefined;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureWorkflowsDirectoryExistsAsync({
|
||||
projectDir,
|
||||
}: {
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await fs.access(path.join(projectDir, '.eas', 'workflows'));
|
||||
} catch {
|
||||
await fs.mkdir(path.join(projectDir, '.eas', 'workflows'), { recursive: true });
|
||||
Log.withTick(`Created directory ${chalk.bold(path.join(projectDir, '.eas', 'workflows'))}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async createWorkflowFileAsync({
|
||||
fileName,
|
||||
projectDir,
|
||||
}: {
|
||||
fileName: string;
|
||||
projectDir: string;
|
||||
}): Promise<void> {
|
||||
WorkflowFile.validateYamlExtension(fileName);
|
||||
|
||||
const filePath = path.join(projectDir, '.eas', 'workflows', fileName);
|
||||
|
||||
filePath = path.join(projectDir, '.eas', 'workflows', fileName);
|
||||
if (await fsExtra.pathExists(filePath)) {
|
||||
throw new Error(`Workflow file already exists: ${filePath}`);
|
||||
Log.error(`Workflow file already exists: ${filePath}`);
|
||||
Log.error('Please choose a different file name.');
|
||||
Log.newLine();
|
||||
fileName = undefined;
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, HELLO_WORLD_TEMPLATE);
|
||||
Log.withTick(`Created ${chalk.bold(filePath)}`);
|
||||
}
|
||||
return { fileName: fileName ?? '', filePath };
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
* ```
|
||||
*/
|
||||
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { Flags } from '@oclif/core';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import chalk from 'chalk';
|
||||
@@ -55,6 +56,7 @@ import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/creat
|
||||
import { EASNonInteractiveFlag, EasJsonOnlyFlag } from '../../commandUtils/flags';
|
||||
import {
|
||||
WorkflowProjectSourceType,
|
||||
WorkflowRevision,
|
||||
WorkflowRunByIdQuery,
|
||||
WorkflowRunStatus,
|
||||
} from '../../graphql/generated';
|
||||
@@ -78,7 +80,8 @@ const EXIT_CODES = {
|
||||
};
|
||||
|
||||
export default class WorkflowRun extends EasCommand {
|
||||
static override description = 'run an EAS workflow';
|
||||
static override description =
|
||||
'run an EAS workflow. The entire local project directory will be packaged and uploaded to EAS servers for the workflow run, unless the --ref flag is used.';
|
||||
|
||||
static override args = [{ name: 'file', description: 'Path to the workflow file to run' }];
|
||||
|
||||
@@ -98,6 +101,11 @@ export default class WorkflowRun extends EasCommand {
|
||||
'Add a parameter in key=value format. Use multiple instances of this flag to set multiple inputs.',
|
||||
summary: 'Set workflow inputs',
|
||||
}),
|
||||
ref: Flags.string({
|
||||
description:
|
||||
"The git reference must exist in the project's git repository, and the workflow file must exist at that reference. When this flag is used, the local project is not uploaded; instead, the workflow is run from the exact state of the project at the chosen reference.",
|
||||
summary: 'Git reference to run the workflow on',
|
||||
}),
|
||||
...EasJsonOnlyFlag,
|
||||
};
|
||||
|
||||
@@ -125,26 +133,70 @@ export default class WorkflowRun extends EasCommand {
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
let yamlConfig: string;
|
||||
try {
|
||||
const workflowFileContents = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath: args.file,
|
||||
});
|
||||
Log.log(`Using workflow file from ${workflowFileContents.filePath}`);
|
||||
yamlConfig = workflowFileContents.yamlConfig;
|
||||
} catch (err) {
|
||||
Log.error('Failed to read workflow file.');
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
const {
|
||||
projectId,
|
||||
exp: { slug: projectName },
|
||||
} = await getDynamicPrivateProjectConfigAsync();
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
let yamlConfig: string;
|
||||
let workflowRunId: string;
|
||||
let workflowRevisionId: string | undefined;
|
||||
let gitRef: string | undefined;
|
||||
|
||||
if (flags.ref) {
|
||||
// Run from git ref
|
||||
const fileName = path.basename(args.file);
|
||||
// Find the real commit, make sure the ref is valid
|
||||
gitRef = (
|
||||
await spawnAsync('git', ['rev-parse', flags.ref], {
|
||||
cwd: projectDir,
|
||||
})
|
||||
).output[0].trim();
|
||||
if (!gitRef) {
|
||||
throw new Error('Failed to resolve git reference');
|
||||
}
|
||||
Log.log(`Using workflow file ${fileName} at ${gitRef}`);
|
||||
let revisionResult: WorkflowRevision | undefined;
|
||||
try {
|
||||
revisionResult = await WorkflowRevisionMutation.getOrCreateWorkflowRevisionFromGitRefAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appId: projectId,
|
||||
fileName,
|
||||
gitRef,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to find or create workflow revision for ${fileName} at ${flags.ref}: ${err}`
|
||||
);
|
||||
}
|
||||
Log.debug(`Workflow revision: ${JSON.stringify(revisionResult, null, 2)}`);
|
||||
if (!revisionResult) {
|
||||
throw new Error(
|
||||
`Failed to find or create workflow revision for ${fileName} at ${flags.ref}`
|
||||
);
|
||||
}
|
||||
yamlConfig = revisionResult.yamlConfig;
|
||||
workflowRevisionId = revisionResult.id;
|
||||
} else {
|
||||
// Run from local file
|
||||
try {
|
||||
const workflowFileContents = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath: args.file,
|
||||
});
|
||||
Log.log(`Using workflow file from ${workflowFileContents.filePath}`);
|
||||
yamlConfig = workflowFileContents.yamlConfig;
|
||||
} catch (err) {
|
||||
Log.error('Failed to read workflow file.');
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate workflow YAML
|
||||
try {
|
||||
await WorkflowRevisionMutation.validateWorkflowYamlConfigAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
@@ -206,81 +258,95 @@ export default class WorkflowRun extends EasCommand {
|
||||
path.relative(await vcsClient.getRootPathAsync(), projectDir) || '.'
|
||||
);
|
||||
|
||||
try {
|
||||
({ projectArchiveBucketKey } = await uploadAccountScopedProjectSourceAsync({
|
||||
graphqlClient,
|
||||
vcsClient,
|
||||
accountId: account.id,
|
||||
}));
|
||||
|
||||
if (await fileExistsAsync(easJsonPath)) {
|
||||
({ fileBucketKey: easJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
filePath: easJsonPath,
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
} else {
|
||||
Log.warn(
|
||||
`⚠ No ${chalk.bold('eas.json')} found in the project directory. Running ${chalk.bold(
|
||||
'type: build'
|
||||
)} jobs will not work. Run ${chalk.bold(
|
||||
'eas build:configure'
|
||||
)} to configure your project for builds.`
|
||||
);
|
||||
}
|
||||
|
||||
if (await fileExistsAsync(packageJsonPath)) {
|
||||
({ fileBucketKey: packageJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
filePath: packageJsonPath,
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
} else {
|
||||
Log.warn(
|
||||
`⚠ No ${chalk.bold(
|
||||
'package.json'
|
||||
)} found in the project directory. It is used to automatically infer best job configuration for your project. You may want to define ${chalk.bold(
|
||||
'image'
|
||||
)} property in your workflow to specify the image to use.`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
Log.error('Failed to upload project sources.');
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
let workflowRunId: string;
|
||||
|
||||
try {
|
||||
({ id: workflowRunId } = await WorkflowRunMutation.createWorkflowRunAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
workflowRevisionInput: {
|
||||
fileName: path.basename(args.file),
|
||||
yamlConfig,
|
||||
},
|
||||
workflowRunInput: {
|
||||
if (gitRef) {
|
||||
// Run from git ref
|
||||
let runResult: { id: string };
|
||||
try {
|
||||
runResult = await WorkflowRunMutation.createWorkflowRunFromGitRefAsync(graphqlClient, {
|
||||
workflowRevisionId: workflowRevisionId ?? '',
|
||||
gitRef,
|
||||
inputs,
|
||||
projectSource: {
|
||||
type: WorkflowProjectSourceType.Gcs,
|
||||
projectArchiveBucketKey,
|
||||
easJsonBucketKey,
|
||||
packageJsonBucketKey,
|
||||
projectRootDirectory,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to create workflow run: ${err}`);
|
||||
}
|
||||
workflowRunId = runResult.id;
|
||||
} else {
|
||||
// Run from local file
|
||||
try {
|
||||
({ projectArchiveBucketKey } = await uploadAccountScopedProjectSourceAsync({
|
||||
graphqlClient,
|
||||
vcsClient,
|
||||
accountId: account.id,
|
||||
}));
|
||||
|
||||
if (await fileExistsAsync(easJsonPath)) {
|
||||
({ fileBucketKey: easJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
filePath: easJsonPath,
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
} else {
|
||||
Log.warn(
|
||||
`⚠ No ${chalk.bold('eas.json')} found in the project directory. Running ${chalk.bold(
|
||||
'type: build'
|
||||
)} jobs will not work. Run ${chalk.bold(
|
||||
'eas build:configure'
|
||||
)} to configure your project for builds.`
|
||||
);
|
||||
}
|
||||
|
||||
if (await fileExistsAsync(packageJsonPath)) {
|
||||
({ fileBucketKey: packageJsonBucketKey } = await uploadAccountScopedFileAsync({
|
||||
graphqlClient,
|
||||
accountId: account.id,
|
||||
filePath: packageJsonPath,
|
||||
maxSizeBytes: 1024 * 1024,
|
||||
}));
|
||||
} else {
|
||||
Log.warn(
|
||||
`⚠ No ${chalk.bold(
|
||||
'package.json'
|
||||
)} found in the project directory. It is used to automatically infer best job configuration for your project. You may want to define ${chalk.bold(
|
||||
'image'
|
||||
)} property in your workflow to specify the image to use.`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
Log.error('Failed to upload project sources.');
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
({ id: workflowRunId } = await WorkflowRunMutation.createWorkflowRunAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
workflowRevisionInput: {
|
||||
fileName: path.basename(args.file),
|
||||
yamlConfig,
|
||||
},
|
||||
},
|
||||
}));
|
||||
workflowRunInput: {
|
||||
inputs,
|
||||
projectSource: {
|
||||
type: WorkflowProjectSourceType.Gcs,
|
||||
projectArchiveBucketKey,
|
||||
easJsonBucketKey,
|
||||
packageJsonBucketKey,
|
||||
projectRootDirectory,
|
||||
},
|
||||
},
|
||||
}));
|
||||
} catch (err) {
|
||||
Log.error('Failed to start the workflow with the API.');
|
||||
|
||||
Log.newLine();
|
||||
Log.log(`See logs: ${link(getWorkflowRunUrl(account.name, projectName, workflowRunId))}`);
|
||||
} catch (err) {
|
||||
Log.error('Failed to start the workflow with the API.');
|
||||
|
||||
throw err;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
Log.log(`See logs: ${link(getWorkflowRunUrl(account.name, projectName, workflowRunId))}`);
|
||||
|
||||
if (!flags.wait) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput({
|
||||
|
||||
+22
-2
@@ -124,6 +124,7 @@ export type Account = {
|
||||
displayName?: Maybe<Scalars['String']['output']>;
|
||||
/** Environment secrets for an account */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
environmentVariableEnvironments: Array<Scalars['EnvironmentVariableEnvironment']['output']>;
|
||||
/** Environment variables for an account */
|
||||
environmentVariables: Array<EnvironmentVariable>;
|
||||
/** Environment variables for an account with decrypted secret values */
|
||||
@@ -1285,6 +1286,7 @@ export type App = Project & {
|
||||
devDomainName?: Maybe<AppDevDomainName>;
|
||||
/** Environment secrets for an app */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
environmentVariableEnvironments: Array<Scalars['EnvironmentVariableEnvironment']['output']>;
|
||||
/** Environment variables for an app */
|
||||
environmentVariables: Array<EnvironmentVariable>;
|
||||
/** Environment variables for an app with decrypted secret values */
|
||||
@@ -9859,6 +9861,15 @@ export type DeleteWebhookMutationVariables = Exact<{
|
||||
|
||||
export type DeleteWebhookMutation = { __typename?: 'RootMutation', webhook: { __typename?: 'WebhookMutation', deleteWebhook: { __typename?: 'DeleteWebhookResult', id: string } } };
|
||||
|
||||
export type GetOrCreateWorkflowRevisionFromGitRefMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
fileName: Scalars['String']['input'];
|
||||
gitRef: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetOrCreateWorkflowRevisionFromGitRefMutation = { __typename?: 'RootMutation', workflowRevision: { __typename?: 'WorkflowRevisionMutation', getOrCreateWorkflowRevisionFromGitRef: { __typename?: 'WorkflowRevision', id: string, yamlConfig: string, blobSha: string, commitSha?: string | null, createdAt: any, workflow: { __typename?: 'Workflow', id: string } } } };
|
||||
|
||||
export type ValidateWorkflowYamlConfigMutationVariables = Exact<{
|
||||
appId: Scalars['ID']['input'];
|
||||
yamlConfig: Scalars['String']['input'];
|
||||
@@ -9876,6 +9887,15 @@ export type CreateWorkflowRunMutationVariables = Exact<{
|
||||
|
||||
export type CreateWorkflowRunMutation = { __typename?: 'RootMutation', workflowRun: { __typename?: 'WorkflowRunMutation', createWorkflowRun: { __typename?: 'WorkflowRun', id: string } } };
|
||||
|
||||
export type CreateWorkflowRunFromGitRefMutationVariables = Exact<{
|
||||
workflowRevisionId: Scalars['ID']['input'];
|
||||
gitRef: Scalars['String']['input'];
|
||||
inputs?: InputMaybe<Scalars['JSONObject']['input']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateWorkflowRunFromGitRefMutation = { __typename?: 'RootMutation', workflowRun: { __typename?: 'WorkflowRunMutation', createWorkflowRunFromGitRef: { __typename?: 'WorkflowRun', id: string } } };
|
||||
|
||||
export type CancelWorkflowRunMutationVariables = Exact<{
|
||||
workflowRunId: Scalars['ID']['input'];
|
||||
}>;
|
||||
@@ -9902,7 +9922,7 @@ export type AppByIdWorkflowsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type AppByIdWorkflowsQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, workflows: Array<{ __typename?: 'Workflow', id: string, name?: string | null, fileName: string, createdAt: any, updatedAt: any }> } } };
|
||||
export type AppByIdWorkflowsQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, workflows: Array<{ __typename?: 'Workflow', id: string, name?: string | null, fileName: string, createdAt: any, updatedAt: any, revisionsPaginated: { __typename?: 'WorkflowRevisionsConnection', edges: Array<{ __typename?: 'WorkflowRevisionEdge', node: { __typename?: 'WorkflowRevision', id: string, blobSha: string, commitSha?: string | null, createdAt: any, yamlConfig: string } }> } }> } } };
|
||||
|
||||
export type AppByIdWorkflowRunsFilteredByStatusQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
@@ -10292,7 +10312,7 @@ export type UpdateChannelBasicInfoFragment = { __typename?: 'UpdateChannel', id:
|
||||
|
||||
export type WebhookFragment = { __typename?: 'Webhook', id: string, event: WebhookType, url: string, createdAt: any, updatedAt: any };
|
||||
|
||||
export type WorkflowFragment = { __typename?: 'Workflow', id: string, name?: string | null, fileName: string, createdAt: any, updatedAt: any };
|
||||
export type WorkflowFragment = { __typename?: 'Workflow', id: string, name?: string | null, fileName: string, createdAt: any, updatedAt: any, revisionsPaginated: { __typename?: 'WorkflowRevisionsConnection', edges: Array<{ __typename?: 'WorkflowRevisionEdge', node: { __typename?: 'WorkflowRevision', id: string, blobSha: string, commitSha?: string | null, createdAt: any, yamlConfig: string } }> } };
|
||||
|
||||
export type WorkflowJobFragment = { __typename?: 'WorkflowJob', id: string, key: string, name: string, status: WorkflowJobStatus, type: WorkflowJobType, outputs: any, createdAt: any, updatedAt: any, workflowRun: { __typename?: 'WorkflowRun', id: string }, turtleJobRun?: { __typename?: 'JobRun', id: string, logFileUrls: Array<string>, artifacts: Array<{ __typename?: 'WorkflowArtifact', id: string, name: string, contentType?: string | null, fileSizeBytes?: number | null, filename: string, downloadUrl?: string | null }>, errors: Array<{ __typename?: 'JobRunError', errorCode: string, message: string }> } | null, turtleBuild?: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'SSOUser', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string }, metrics?: { __typename?: 'BuildMetrics', buildWaitTime?: number | null, buildQueueTime?: number | null, buildDuration?: number | null } | null } | null, errors: Array<{ __typename?: 'WorkflowJobError', title: string, message: string }> };
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ import { z } from 'zod';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
GetOrCreateWorkflowRevisionFromGitRefMutation,
|
||||
GetOrCreateWorkflowRevisionFromGitRefMutationVariables,
|
||||
ValidateWorkflowYamlConfigMutation,
|
||||
ValidateWorkflowYamlConfigMutationVariables,
|
||||
WorkflowRevision,
|
||||
} from '../generated';
|
||||
|
||||
export namespace WorkflowRevisionMutation {
|
||||
@@ -17,6 +20,61 @@ export namespace WorkflowRevisionMutation {
|
||||
}),
|
||||
});
|
||||
|
||||
export async function getOrCreateWorkflowRevisionFromGitRefAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
fileName,
|
||||
gitRef,
|
||||
}: {
|
||||
appId: string;
|
||||
fileName: string;
|
||||
gitRef: string;
|
||||
}
|
||||
): Promise<WorkflowRevision | undefined> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<
|
||||
GetOrCreateWorkflowRevisionFromGitRefMutation,
|
||||
GetOrCreateWorkflowRevisionFromGitRefMutationVariables
|
||||
>(
|
||||
gql`
|
||||
mutation GetOrCreateWorkflowRevisionFromGitRef(
|
||||
$appId: ID!
|
||||
$fileName: String!
|
||||
$gitRef: String!
|
||||
) {
|
||||
workflowRevision {
|
||||
getOrCreateWorkflowRevisionFromGitRef(
|
||||
appId: $appId
|
||||
fileName: $fileName
|
||||
gitRef: $gitRef
|
||||
) {
|
||||
id
|
||||
yamlConfig
|
||||
blobSha
|
||||
commitSha
|
||||
createdAt
|
||||
workflow {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
appId,
|
||||
fileName,
|
||||
gitRef,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return (
|
||||
(data.workflowRevision?.getOrCreateWorkflowRevisionFromGitRef as WorkflowRevision) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
export async function validateWorkflowYamlConfigAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
CancelWorkflowRunMutation,
|
||||
CancelWorkflowRunMutationVariables,
|
||||
CreateWorkflowRunFromGitRefMutation,
|
||||
CreateWorkflowRunFromGitRefMutationVariables,
|
||||
CreateWorkflowRunMutation,
|
||||
CreateWorkflowRunMutationVariables,
|
||||
WorkflowRevisionInput,
|
||||
@@ -54,6 +56,53 @@ export namespace WorkflowRunMutation {
|
||||
);
|
||||
return { id: data.workflowRun.createWorkflowRun.id };
|
||||
}
|
||||
|
||||
export async function createWorkflowRunFromGitRefAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
workflowRevisionId,
|
||||
gitRef,
|
||||
inputs,
|
||||
}: {
|
||||
workflowRevisionId: string;
|
||||
gitRef: string;
|
||||
inputs?: Record<string, any>;
|
||||
}
|
||||
): Promise<{ id: string }> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<
|
||||
CreateWorkflowRunFromGitRefMutation,
|
||||
CreateWorkflowRunFromGitRefMutationVariables
|
||||
>(
|
||||
gql`
|
||||
mutation CreateWorkflowRunFromGitRef(
|
||||
$workflowRevisionId: ID!
|
||||
$gitRef: String!
|
||||
$inputs: JSONObject
|
||||
) {
|
||||
workflowRun {
|
||||
createWorkflowRunFromGitRef(
|
||||
workflowRevisionId: $workflowRevisionId
|
||||
gitRef: $gitRef
|
||||
inputs: $inputs
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
workflowRevisionId,
|
||||
gitRef,
|
||||
inputs,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return { id: data.workflowRun.createWorkflowRunFromGitRef.id };
|
||||
}
|
||||
|
||||
export async function cancelWorkflowRunAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
|
||||
@@ -7,5 +7,16 @@ export const WorkflowFragmentNode = gql`
|
||||
fileName
|
||||
createdAt
|
||||
updatedAt
|
||||
revisionsPaginated(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
blobSha
|
||||
commitSha
|
||||
createdAt
|
||||
yamlConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -12,6 +12,8 @@ import { EnvironmentVariableEnvironment } from '../build/utils/environment';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
|
||||
|
||||
const EXPO_ROUTES_PATHS = new Set(['_expo/routes.json', '_expo/.routes.json', '.expo-routes.json']);
|
||||
|
||||
/** Returns whether a file or folder is ignored */
|
||||
function isIgnoredName(name: string): boolean {
|
||||
switch (name) {
|
||||
@@ -114,6 +116,8 @@ export async function collectAssetsAsync(
|
||||
throw new Error(
|
||||
`Upload of "${file.normalizedPath}" aborted: File size is greater than the upload limit (>500MB)`
|
||||
);
|
||||
} else if (EXPO_ROUTES_PATHS.has(file.normalizedPath)) {
|
||||
continue;
|
||||
}
|
||||
const sha512$ = computeSha512HashAsync(file.path);
|
||||
const contentType$ = determineMimeTypeAsync(file.path);
|
||||
@@ -129,6 +133,31 @@ export async function collectAssetsAsync(
|
||||
return assets;
|
||||
}
|
||||
|
||||
export interface RoutesConfigEntry {
|
||||
headers?: Record<string, unknown>;
|
||||
redirects?: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export async function getRoutesConfigAsync(
|
||||
assetPath: string | undefined
|
||||
): Promise<RoutesConfigEntry | null> {
|
||||
if (assetPath) {
|
||||
for (const candidatePath of EXPO_ROUTES_PATHS) {
|
||||
const targetPath = path.resolve(assetPath, candidatePath);
|
||||
let json: unknown;
|
||||
try {
|
||||
json = JSON.parse(await fs.promises.readFile(targetPath, 'utf8'));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (typeof json === 'object' && json) {
|
||||
return json as RoutesConfigEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Mapping of normalized file paths to a SHA512 hash */
|
||||
export type AssetMap = Record<
|
||||
string,
|
||||
|
||||
Reference in new issue
Block a user