mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 17:50:32 -10:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cacd48bf66 | ||
|
|
bc213a51b9 | ||
|
|
ddd8bb7fdd | ||
|
|
ed251cb0d1 | ||
|
|
5176495e9e | ||
|
|
32211614eb | ||
|
|
ce5b48244c | ||
|
|
ece1d00ca7 | ||
|
|
82dd218a2b | ||
|
|
9f763c6957 | ||
|
|
e74c93d80c | ||
|
|
3d691d602c | ||
|
|
7fde621858 |
No files matched your search
@@ -8,9 +8,13 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- [eas-cli] Add `eas integrations:convex` commands to manage Convex integrations for EAS projects. ([#3575](https://github.com/expo/eas-cli/pull/3575) by [@fiberjw](https://github.com/fiberjw))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Create portable project archives on all platforms to normalize cross-platform tar metadata and permissions. ([#3234](https://github.com/expo/eas-cli/pull/3234) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
- [eas-cli] Remove hardcoded `builderEnvironment.image` override in `eas build:resign`. ([#3661](https://github.com/expo/eas-cli/pull/3661) by [@hSATAC](https://github.com/hSATAC))
|
||||
- [eas-cli] Fix `eas update --json` intermittently failing with JSON parse errors during "Computing project fingerprints" by passing `silent: true` to `@expo/fingerprint` to suppress subprocess stdout pollution. ([#3659](https://github.com/expo/eas-cli/pull/3659) by [@Mookiies](https://github.com/Mookiies))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "18.9.1",
|
||||
"version": "18.10.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/build-tools",
|
||||
"version": "18.9.0",
|
||||
"version": "18.10.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
|
||||
@@ -50,7 +50,7 @@ yarn generate-graphql-code # Generate TypeScript types from GraphQL schema
|
||||
yarn verify-graphql-code # Verify GraphQL code is up to date (CI check)
|
||||
```
|
||||
|
||||
The GraphQL schema is fetched from `https://staging-api.expo.dev/graphql` and generates:
|
||||
The GraphQL schema is fetched from the API URL resolved by `src/api.ts` and generates:
|
||||
|
||||
- `src/graphql/generated.ts` - 499KB+ of TypeScript types
|
||||
- Types for all queries, mutations, and fragments
|
||||
|
||||
+223
-93
@@ -120,6 +120,13 @@ eas --help COMMAND
|
||||
* [`eas integrations:asc:connect`](#eas-integrationsascconnect)
|
||||
* [`eas integrations:asc:disconnect`](#eas-integrationsascdisconnect)
|
||||
* [`eas integrations:asc:status`](#eas-integrationsascstatus)
|
||||
* [`eas integrations:convex:connect`](#eas-integrationsconvexconnect)
|
||||
* [`eas integrations:convex:dashboard`](#eas-integrationsconvexdashboard)
|
||||
* [`eas integrations:convex:project`](#eas-integrationsconvexproject)
|
||||
* [`eas integrations:convex:project:delete`](#eas-integrationsconvexprojectdelete)
|
||||
* [`eas integrations:convex:team`](#eas-integrationsconvexteam)
|
||||
* [`eas integrations:convex:team:delete [CONVEX_TEAM]`](#eas-integrationsconvexteamdelete-convex_team)
|
||||
* [`eas integrations:convex:team:invite [CONVEX_TEAM]`](#eas-integrationsconvexteaminvite-convex_team)
|
||||
* [`eas login`](#eas-login)
|
||||
* [`eas logout`](#eas-logout)
|
||||
* [`eas metadata:lint`](#eas-metadatalint)
|
||||
@@ -183,7 +190,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -200,7 +207,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:usage [ACCOUNT_NAME]`
|
||||
|
||||
@@ -222,7 +229,7 @@ DESCRIPTION
|
||||
view account usage and billing for the current cycle
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -239,7 +246,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -253,7 +260,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -305,7 +312,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -326,7 +333,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -346,7 +353,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -366,7 +373,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -389,7 +396,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -427,7 +434,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -447,7 +454,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -465,7 +472,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -485,7 +492,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:dev`
|
||||
|
||||
@@ -507,7 +514,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:download`
|
||||
|
||||
@@ -534,7 +541,7 @@ DESCRIPTION
|
||||
download a simulator/emulator build by build ID or fingerprint hash
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -572,7 +579,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -612,7 +619,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -642,7 +649,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -668,7 +675,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -720,7 +727,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -738,7 +745,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -756,7 +763,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -773,7 +780,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -794,7 +801,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:delete [NAME]`
|
||||
|
||||
@@ -815,7 +822,7 @@ DESCRIPTION
|
||||
Delete a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -837,7 +844,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:insights`
|
||||
|
||||
@@ -861,7 +868,7 @@ DESCRIPTION
|
||||
display adoption, crash, and unique-user insights for a channel + runtime version
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/channel/insights.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/channel/insights.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -881,7 +888,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -903,7 +910,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -925,7 +932,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -960,7 +967,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -983,7 +990,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -1004,7 +1011,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/v18.9.1/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -1021,7 +1028,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -1039,7 +1046,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [options]`
|
||||
|
||||
@@ -1068,7 +1075,7 @@ ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
@@ -1093,7 +1100,7 @@ ALIASES
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
|
||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||
|
||||
@@ -1114,7 +1121,7 @@ ALIASES
|
||||
$ eas worker:alias:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
|
||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||
|
||||
@@ -1135,7 +1142,7 @@ ALIASES
|
||||
$ eas worker:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
@@ -1172,7 +1179,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -1192,7 +1199,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -1213,7 +1220,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -1234,7 +1241,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -1248,7 +1255,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -1262,7 +1269,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1294,7 +1301,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1320,7 +1327,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1342,7 +1349,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1370,7 +1377,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1398,7 +1405,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1421,7 +1428,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1443,7 +1450,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1477,7 +1484,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||
|
||||
@@ -1521,7 +1528,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas fingerprint:generate`
|
||||
|
||||
@@ -1552,7 +1559,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1631,7 +1638,7 @@ DESCRIPTION
|
||||
connect a project to an App Store Connect app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
|
||||
|
||||
## `eas integrations:asc:disconnect`
|
||||
|
||||
@@ -1650,7 +1657,7 @@ DESCRIPTION
|
||||
disconnect the current project from its App Store Connect app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
|
||||
|
||||
## `eas integrations:asc:status`
|
||||
|
||||
@@ -1668,7 +1675,130 @@ DESCRIPTION
|
||||
show the App Store Connect app link status for the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/integrations/asc/status.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
|
||||
|
||||
## `eas integrations:convex:connect`
|
||||
|
||||
connect Convex to your Expo project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:connect [--non-interactive] [--region aws-us-east-1|aws-eu-west-1] [--team-name <value>]
|
||||
[--project-name <value>]
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--project-name=<value> Name for the Convex project (defaults to app slug)
|
||||
--region=<option> Convex deployment region (e.g. aws-us-east-1, aws-eu-west-1)
|
||||
<options: aws-us-east-1|aws-eu-west-1>
|
||||
--team-name=<value> Name for the new Convex team (defaults to EAS account name)
|
||||
|
||||
DESCRIPTION
|
||||
connect Convex to your Expo project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/connect.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
|
||||
|
||||
## `eas integrations:convex:dashboard`
|
||||
|
||||
open the Convex dashboard for the linked Convex project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:dashboard
|
||||
|
||||
DESCRIPTION
|
||||
open the Convex dashboard for the linked Convex project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/dashboard.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
|
||||
|
||||
## `eas integrations:convex:project`
|
||||
|
||||
display the Convex project linked to the current Expo app
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:project
|
||||
|
||||
DESCRIPTION
|
||||
display the Convex project linked to the current Expo app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
|
||||
|
||||
## `eas integrations:convex:project:delete`
|
||||
|
||||
remove the Convex project link for the current Expo app from EAS servers
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:project:delete [--non-interactive] [-y]
|
||||
|
||||
FLAGS
|
||||
-y, --yes Skip confirmation prompt
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
remove the Convex project link for the current Expo app from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project/delete.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team`
|
||||
|
||||
display Convex teams linked to the current Expo app's owner account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:team
|
||||
|
||||
DESCRIPTION
|
||||
display Convex teams linked to the current Expo app's owner account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
|
||||
|
||||
## `eas integrations:convex:team:delete [CONVEX_TEAM]`
|
||||
|
||||
remove a Convex team link from the current Expo app owner account's EAS servers
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:team:delete [CONVEX_TEAM] [--non-interactive] [-y]
|
||||
|
||||
ARGUMENTS
|
||||
[CONVEX_TEAM] Slug of the Convex team to remove
|
||||
|
||||
FLAGS
|
||||
-y, --yes Skip confirmation prompt
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
remove a Convex team link from the current Expo app owner account's EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/delete.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team:invite [CONVEX_TEAM]`
|
||||
|
||||
send a Convex team invitation to your verified email address
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas integrations:convex:team:invite [CONVEX_TEAM] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
[CONVEX_TEAM] Slug of the Convex team to invite yourself to
|
||||
|
||||
FLAGS
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
send a Convex team invitation to your verified email address
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/invite.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
|
||||
|
||||
## `eas login`
|
||||
|
||||
@@ -1720,7 +1850,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1738,7 +1868,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1756,7 +1886,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas new [PATH]`
|
||||
|
||||
@@ -1808,7 +1938,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/v18.9.1/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1822,7 +1952,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1845,7 +1975,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:new [PATH]`
|
||||
|
||||
@@ -1869,7 +1999,7 @@ ALIASES
|
||||
$ eas new
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/project/new.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/project/new.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1887,7 +2017,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1920,7 +2050,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1963,7 +2093,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1984,7 +2114,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -2005,7 +2135,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -2030,7 +2160,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:insights GROUPID`
|
||||
|
||||
@@ -2057,7 +2187,7 @@ DESCRIPTION
|
||||
display launch, crash, unique-user, and size insights for an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/update/insights.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/update/insights.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -2084,7 +2214,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -2122,7 +2252,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:revert-update-rollout`
|
||||
|
||||
@@ -2150,7 +2280,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -2180,7 +2310,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -2201,7 +2331,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -2225,7 +2355,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas upload`
|
||||
|
||||
@@ -2246,7 +2376,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/v18.9.1/packages/eas-cli/src/commands/upload.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -2268,7 +2398,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -2288,7 +2418,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -2307,7 +2437,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -2330,7 +2460,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -2347,7 +2477,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -2468,7 +2598,7 @@ DESCRIPTION
|
||||
to cancel.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
@@ -2488,7 +2618,7 @@ 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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:logs [ID]`
|
||||
|
||||
@@ -2512,7 +2642,7 @@ 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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
|
||||
## `eas workflow:run FILE`
|
||||
|
||||
@@ -2552,7 +2682,7 @@ FLAG DESCRIPTIONS
|
||||
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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:runs`
|
||||
|
||||
@@ -2574,7 +2704,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
|
||||
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
||||
|
||||
@@ -2602,7 +2732,7 @@ FLAG DESCRIPTIONS
|
||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.9.1/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.10.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -2622,7 +2752,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
|
||||
## `eas workflow:view [ID]`
|
||||
|
||||
@@ -2644,5 +2774,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/v18.9.1/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/v18.10.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { CodegenConfig } from '@graphql-codegen/cli';
|
||||
|
||||
import { getExpoApiBaseUrl } from './src/api';
|
||||
|
||||
const schema =
|
||||
process.env.GRAPHQL_SCHEMA_URL ?? new URL('/graphql', getExpoApiBaseUrl()).toString();
|
||||
|
||||
const config: CodegenConfig = {
|
||||
overwrite: true,
|
||||
schema,
|
||||
documents: ['src/**/!(*.d).ts'],
|
||||
generates: {
|
||||
'src/graphql/generated.ts': {
|
||||
plugins: ['typescript', 'typescript-operations'],
|
||||
config: {
|
||||
dedupeOperationSuffix: true,
|
||||
},
|
||||
hooks: {
|
||||
afterOneFileWrite: ['node ./scripts/annotate-graphql-codegen.js'],
|
||||
},
|
||||
},
|
||||
'./graphql.schema.json': {
|
||||
plugins: ['introspection'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,17 +0,0 @@
|
||||
overwrite: true
|
||||
schema: 'https://staging-api.expo.dev/graphql'
|
||||
documents:
|
||||
- 'src/**/!(*.d).ts'
|
||||
generates:
|
||||
src/graphql/generated.ts:
|
||||
plugins:
|
||||
- 'typescript'
|
||||
- 'typescript-operations'
|
||||
config:
|
||||
dedupeOperationSuffix: true
|
||||
hooks:
|
||||
afterOneFileWrite:
|
||||
- 'node ./scripts/annotate-graphql-codegen.js'
|
||||
./graphql.schema.json:
|
||||
plugins:
|
||||
- 'introspection'
|
||||
Generated
+324
-1
@@ -25488,6 +25488,18 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "resolvedImage",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "resourceClass",
|
||||
"description": "The builder resource class requested by the developer",
|
||||
@@ -25603,7 +25615,7 @@
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": true,
|
||||
"deprecationReason": "Check logs instead."
|
||||
"deprecationReason": "Use 'resolvedImage' for the concrete image the build runs on."
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
@@ -29825,6 +29837,83 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ConvexProjectMutation",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "deleteConvexProject",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "convexProjectId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "setupConvexProject",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "SetupConvexProjectInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "SetupConvexProjectResult",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "ConvexTeamConnection",
|
||||
@@ -29862,6 +29951,38 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexTeamName",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexTeamSlug",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": null,
|
||||
@@ -29894,6 +30015,30 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "invitedAt",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "invitedEmail",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"description": null,
|
||||
@@ -55702,6 +55847,22 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexProject",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ConvexProjectMutation",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexTeamConnection",
|
||||
"description": null,
|
||||
@@ -60001,6 +60162,152 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "SetupConvexProjectInput",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "appId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexTeamConnectionId",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "deploymentRegion",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "projectName",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "SetupConvexProjectResult",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "convexDeploymentName",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexDeploymentUrl",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "convexProject",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ConvexProject",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "deployKey",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "SizeBreakdownCategory",
|
||||
@@ -62061,6 +62368,22 @@
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "includedAgentCreditsInCents",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isDowngrading",
|
||||
"description": null,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"version": "18.9.1",
|
||||
"version": "18.10.0",
|
||||
"description": "EAS command line tool",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -37,7 +37,7 @@
|
||||
"typecheck-for-build": "tsc --project tsconfig.build.json",
|
||||
"test": "jest",
|
||||
"version": "yarn run -T oclif readme && node scripts/patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.ts",
|
||||
"verify-graphql-code": "./scripts/verify-graphql.sh",
|
||||
"clean": "rimraf dist build tmp node_modules yarn-error.log \"*.tsbuildinfo\"",
|
||||
"copy-new-templates": "rimraf build/commandUtils/new/templates && mkdir -p build/commandUtils/new && cp -r src/commandUtils/new/templates build/commandUtils/new"
|
||||
@@ -202,7 +202,7 @@
|
||||
"description": "manage project and account environment variables"
|
||||
},
|
||||
"integrations": {
|
||||
"description": "manage service connections"
|
||||
"description": "manage third-party service integrations"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "manage store configuration"
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
/*
|
||||
* Manual Convex integration E2E runner.
|
||||
*
|
||||
* Run from packages/eas-cli with:
|
||||
* yarn ts-node scripts/convex-e2e.ts
|
||||
*
|
||||
* This uses the deployed EAS API and real authenticated EAS/Convex-backed
|
||||
* resources. Cleanup removes EAS-side links when possible, but it does not
|
||||
* destroy Convex-side resources.
|
||||
*
|
||||
* Options:
|
||||
* CONVEX_E2E_KEEP_PROJECT=1 keeps the temp Expo project and skips cleanup.
|
||||
* CONVEX_E2E_SKIP_DASHBOARD=1 skips opening the Convex dashboard.
|
||||
* CONVEX_E2E_ACCOUNT=<account-name> sets the Expo app owner before eas init.
|
||||
*/
|
||||
|
||||
type RunOptions = {
|
||||
cwd?: string;
|
||||
allowFailure?: boolean;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
};
|
||||
|
||||
type RunResult = {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
exitCode: number | null;
|
||||
};
|
||||
|
||||
const KEEP_PROJECT = process.env.CONVEX_E2E_KEEP_PROJECT === '1';
|
||||
const SKIP_DASHBOARD = process.env.CONVEX_E2E_SKIP_DASHBOARD === '1';
|
||||
const ACCOUNT = process.env.CONVEX_E2E_ACCOUNT;
|
||||
const SCRIPT_DIR = __dirname;
|
||||
const EAS_CLI_ROOT = path.resolve(SCRIPT_DIR, '..');
|
||||
const LOCAL_EAS_BIN = path.join(EAS_CLI_ROOT, 'bin', 'run');
|
||||
const TEMP_ROOT_PREFIX = path.join(os.tmpdir(), 'eas-cli-convex-e2e-');
|
||||
|
||||
let tempRoot: string | null = null;
|
||||
let projectDir: string | null = null;
|
||||
let initializedProject = false;
|
||||
let initialConvexTeamSlugs: string[] = [];
|
||||
let createdConvexTeamSlug: string | null = null;
|
||||
|
||||
async function mainAsync(): Promise<void> {
|
||||
try {
|
||||
await assertFileExistsAsync(
|
||||
LOCAL_EAS_BIN,
|
||||
`Local EAS CLI binary not found at ${LOCAL_EAS_BIN}`
|
||||
);
|
||||
await runEasAsync(['whoami']);
|
||||
|
||||
const testName = `convex-e2e-${Date.now().toString(36)}`;
|
||||
tempRoot = await fs.mkdtemp(TEMP_ROOT_PREFIX);
|
||||
projectDir = path.join(tempRoot, testName);
|
||||
|
||||
logStep(`Creating fresh Expo project ${testName}`);
|
||||
await runAsync('npx', ['--yes', 'create-expo-app@latest', projectDir, '--template', 'blank'], {
|
||||
cwd: tempRoot,
|
||||
env: getNpxEnvironment(),
|
||||
});
|
||||
|
||||
await updateAppJsonAsync(projectDir, testName);
|
||||
|
||||
logStep('Creating/linking EAS project');
|
||||
await runEasAsync(['init', '--non-interactive', '--force'], projectDir);
|
||||
initializedProject = true;
|
||||
|
||||
initialConvexTeamSlugs = parseConvexTeamSlugs(
|
||||
(await runEasAsync(['integrations:convex:team'], projectDir)).stdout
|
||||
);
|
||||
|
||||
logStep('Connecting Convex');
|
||||
await runEasAsync(
|
||||
['integrations:convex:connect', '--non-interactive', '--project-name', testName],
|
||||
projectDir
|
||||
);
|
||||
|
||||
await assertEnvLocalAsync(projectDir);
|
||||
await assertPackageJsonDependencyAsync(projectDir, 'convex');
|
||||
|
||||
const teamOutput = await runEasAsync(['integrations:convex:team'], projectDir);
|
||||
const projectOutput = await runEasAsync(['integrations:convex:project'], projectDir);
|
||||
assertIncludes(
|
||||
projectOutput.stdout,
|
||||
'Dashboard',
|
||||
'Expected project output to include dashboard'
|
||||
);
|
||||
|
||||
const teamSlugsAfterConnect = parseConvexTeamSlugs(teamOutput.stdout);
|
||||
const newTeamSlugs = teamSlugsAfterConnect.filter(
|
||||
slug => !initialConvexTeamSlugs.includes(slug)
|
||||
);
|
||||
createdConvexTeamSlug = newTeamSlugs[0] ?? null;
|
||||
const convexTeamSlug = createdConvexTeamSlug ?? teamSlugsAfterConnect[0];
|
||||
|
||||
if (!convexTeamSlug) {
|
||||
throw new Error('Could not determine a Convex team slug from team output.');
|
||||
}
|
||||
|
||||
await runEasAsync(
|
||||
['integrations:convex:team:invite', convexTeamSlug, '--non-interactive'],
|
||||
projectDir
|
||||
);
|
||||
|
||||
if (SKIP_DASHBOARD) {
|
||||
logStep('Skipping dashboard command because CONVEX_E2E_SKIP_DASHBOARD=1');
|
||||
} else {
|
||||
await runEasAsync(['integrations:convex:dashboard'], projectDir);
|
||||
}
|
||||
|
||||
logStep('Convex E2E flow completed successfully');
|
||||
} finally {
|
||||
await cleanupAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupAsync(): Promise<void> {
|
||||
if (!projectDir || !tempRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEEP_PROJECT) {
|
||||
logStep('Skipping cleanup because CONVEX_E2E_KEEP_PROJECT=1');
|
||||
log(`Temp project kept at: ${projectDir}`);
|
||||
log('EAS-side links and Convex-side resources were left intact.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (initializedProject) {
|
||||
logStep('Cleaning up EAS-side Convex links');
|
||||
const convexTeamSlugToDelete =
|
||||
createdConvexTeamSlug ?? (await getCreatedConvexTeamSlugForCleanupAsync());
|
||||
|
||||
await runCleanupCommandAsync([
|
||||
'integrations:convex:project:delete',
|
||||
'--non-interactive',
|
||||
'--yes',
|
||||
]);
|
||||
|
||||
if (convexTeamSlugToDelete) {
|
||||
await runCleanupCommandAsync([
|
||||
'integrations:convex:team:delete',
|
||||
convexTeamSlugToDelete,
|
||||
'--non-interactive',
|
||||
'--yes',
|
||||
]);
|
||||
} else {
|
||||
warn(
|
||||
'No newly-created Convex team link was detected. Skipping team link deletion to avoid removing a pre-existing EAS-side link.'
|
||||
);
|
||||
}
|
||||
|
||||
warn('Convex-side resources are not destroyed by this cleanup.');
|
||||
}
|
||||
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
log(`Removed temp project directory: ${tempRoot}`);
|
||||
}
|
||||
|
||||
async function getCreatedConvexTeamSlugForCleanupAsync(): Promise<string | null> {
|
||||
if (!projectDir) {
|
||||
return null;
|
||||
}
|
||||
const result = await runEasAsync(['integrations:convex:team'], projectDir, {
|
||||
allowFailure: true,
|
||||
});
|
||||
if (result.exitCode !== 0) {
|
||||
warn('Could not inspect Convex team links during cleanup.');
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
parseConvexTeamSlugs(result.stdout).find(slug => !initialConvexTeamSlugs.includes(slug)) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
async function runCleanupCommandAsync(args: string[]): Promise<void> {
|
||||
try {
|
||||
const result = await runEasAsync(args, projectDir ?? undefined, { allowFailure: true });
|
||||
if (result.exitCode !== 0) {
|
||||
warn(`Cleanup command exited with ${result.exitCode}: ${args.join(' ')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
warn(`Cleanup command failed: ${args.join(' ')}`);
|
||||
warn(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
async function runEasAsync(
|
||||
args: string[],
|
||||
cwd?: string,
|
||||
options: RunOptions = {}
|
||||
): Promise<RunResult> {
|
||||
return await runAsync(LOCAL_EAS_BIN, args, { cwd, ...options });
|
||||
}
|
||||
|
||||
async function runAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
{ cwd, allowFailure = false, env = {} }: RunOptions = {}
|
||||
): Promise<RunResult> {
|
||||
const printableCommand = [command, ...args].join(' ');
|
||||
log(`\n$ ${printableCommand}`);
|
||||
if (cwd) {
|
||||
log(`cwd: ${cwd}`);
|
||||
}
|
||||
|
||||
return await new Promise<RunResult>((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...env,
|
||||
CI: '1',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', data => {
|
||||
const chunk = data.toString();
|
||||
stdout += chunk;
|
||||
process.stdout.write(redact(chunk));
|
||||
});
|
||||
|
||||
child.stderr.on('data', data => {
|
||||
const chunk = data.toString();
|
||||
stderr += chunk;
|
||||
process.stderr.write(redact(chunk));
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
child.on('close', exitCode => {
|
||||
const result = { stdout, stderr, exitCode };
|
||||
if (!allowFailure && exitCode !== 0) {
|
||||
reject(new Error(`Command failed with exit code ${exitCode}: ${printableCommand}`));
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function updateAppJsonAsync(appDir: string, testName: string): Promise<void> {
|
||||
const appJsonPath = path.join(appDir, 'app.json');
|
||||
const rawAppJson = await fs.readFile(appJsonPath, 'utf8');
|
||||
const appJson = JSON.parse(rawAppJson);
|
||||
appJson.expo = {
|
||||
...appJson.expo,
|
||||
name: testName,
|
||||
slug: testName,
|
||||
...(ACCOUNT ? { owner: ACCOUNT } : {}),
|
||||
};
|
||||
await fs.writeFile(appJsonPath, `${JSON.stringify(appJson, null, 2)}\n`);
|
||||
}
|
||||
|
||||
async function assertEnvLocalAsync(appDir: string): Promise<void> {
|
||||
const envLocalPath = path.join(appDir, '.env.local');
|
||||
const envLocal = await fs.readFile(envLocalPath, 'utf8');
|
||||
assertIncludes(
|
||||
envLocal,
|
||||
'CONVEX_DEPLOY_KEY=',
|
||||
'Expected .env.local to contain CONVEX_DEPLOY_KEY'
|
||||
);
|
||||
assertIncludes(
|
||||
envLocal,
|
||||
'EXPO_PUBLIC_CONVEX_URL=',
|
||||
'Expected .env.local to contain EXPO_PUBLIC_CONVEX_URL'
|
||||
);
|
||||
log('\nVerified .env.local contains Convex environment variables:');
|
||||
log(redact(envLocal));
|
||||
}
|
||||
|
||||
async function assertPackageJsonDependencyAsync(
|
||||
appDir: string,
|
||||
packageName: string
|
||||
): Promise<void> {
|
||||
const packageJsonPath = path.join(appDir, 'package.json');
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
const dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
if (!dependencies[packageName]) {
|
||||
throw new Error(`Expected package.json to include ${packageName}`);
|
||||
}
|
||||
log(`Verified package.json includes ${packageName}@${dependencies[packageName]}`);
|
||||
}
|
||||
|
||||
function parseConvexTeamSlugs(output: string): string[] {
|
||||
const strippedOutput = stripAnsi(output);
|
||||
return Array.from(strippedOutput.matchAll(/^Team:\s+.+?\s\/\s(.+)$/gm), match => match[1].trim());
|
||||
}
|
||||
|
||||
function assertIncludes(value: string, expected: string, message: string): void {
|
||||
if (!value.includes(expected)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertFileExistsAsync(filePath: string, message: string): Promise<void> {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function logStep(message: string): void {
|
||||
log(`\n==> ${message}`);
|
||||
}
|
||||
|
||||
function log(message: string): void {
|
||||
process.stdout.write(`${message}\n`);
|
||||
}
|
||||
|
||||
function warn(message: string): void {
|
||||
process.stderr.write(`${message}\n`);
|
||||
}
|
||||
|
||||
function redact(value: string): string {
|
||||
return value
|
||||
.replace(/^CONVEX_DEPLOY_KEY=.*$/gm, 'CONVEX_DEPLOY_KEY=<redacted>')
|
||||
.replace(/dev:[^\s|]+\|[^\s]+/g, 'dev:<redacted>');
|
||||
}
|
||||
|
||||
function stripAnsi(value: string): string {
|
||||
return value.replace(/\u001b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
function getNpxEnvironment(): NodeJS.ProcessEnv {
|
||||
return {
|
||||
npm_config_user_agent: `npm/10 node/${process.versions.node} ${process.platform} ${process.arch}`,
|
||||
npm_execpath: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
mainAsync().catch(error => {
|
||||
warn(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import * as tar from 'tar';
|
||||
|
||||
import { Client } from '../../../vcs/vcs';
|
||||
import { makeProjectTarballAsync } from '../repository';
|
||||
|
||||
jest.mock('../../../ora', () => ({
|
||||
ora: jest.fn(() => ({
|
||||
fail: jest.fn(),
|
||||
isSpinning: false,
|
||||
start: jest.fn(),
|
||||
succeed: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
class FakeVcsClient extends Client {
|
||||
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
|
||||
await fs.mkdirp(path.join(destinationPath, 'bin'));
|
||||
await fs.mkdirp(path.join(destinationPath, 'read-only-dir'));
|
||||
|
||||
await fs.writeFile(path.join(destinationPath, 'bin', 'postcheckout.sh'), '#!/bin/sh\necho hi\n');
|
||||
await fs.writeFile(path.join(destinationPath, 'regular-file.txt'), 'regular file\n');
|
||||
await fs.writeFile(path.join(destinationPath, 'read-only-dir', 'child.txt'), 'child file\n');
|
||||
|
||||
await fs.chmod(path.join(destinationPath, 'bin', 'postcheckout.sh'), 0o755);
|
||||
await fs.chmod(path.join(destinationPath, 'regular-file.txt'), 0o644);
|
||||
await fs.chmod(path.join(destinationPath, 'read-only-dir'), 0o555);
|
||||
}
|
||||
|
||||
public async getRootPathAsync(): Promise<string> {
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
public canGetLastCommitMessage(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
describe(makeProjectTarballAsync, () => {
|
||||
it('creates portable project archives while preserving executable files', async () => {
|
||||
const removeAsync = fs.remove.bind(fs);
|
||||
const removeSpy = jest.spyOn(fs, 'remove').mockImplementation(async targetPath => {
|
||||
await fs.chmod(path.join(targetPath.toString(), 'read-only-dir'), 0o755).catch(() => {});
|
||||
await removeAsync(targetPath);
|
||||
});
|
||||
const projectTarball = await makeProjectTarballAsync(new FakeVcsClient());
|
||||
const entries = new Map<string, tar.ReadEntry>();
|
||||
|
||||
try {
|
||||
await tar.list({
|
||||
file: projectTarball.path,
|
||||
onentry: entry => {
|
||||
entries.set(entry.path, entry);
|
||||
},
|
||||
});
|
||||
|
||||
expect(entries.get('project/bin/postcheckout.sh')?.mode).toBe(0o755);
|
||||
expect(entries.get('project/regular-file.txt')?.mode).toBe(0o644);
|
||||
expect(entries.get('project/read-only-dir/')?.mode).toBe(0o755);
|
||||
expect(entries.has('project/read-only-dir/child.txt')).toBe(true);
|
||||
|
||||
for (const entry of entries.values()) {
|
||||
expect(entry.path.startsWith('project/')).toBe(true);
|
||||
expect(entry.uid).toBeUndefined();
|
||||
expect(entry.gid).toBeUndefined();
|
||||
expect(entry.uname).toBe('');
|
||||
expect(entry.gname).toBe('');
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(projectTarball.path);
|
||||
removeSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -143,9 +143,16 @@ export async function makeProjectTarballAsync(vcsClient: Client): Promise<LocalF
|
||||
|
||||
try {
|
||||
await vcsClient.makeShallowCopyAsync(shallowClonePath);
|
||||
await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
|
||||
'.',
|
||||
]);
|
||||
await tar.create(
|
||||
{
|
||||
cwd: shallowClonePath,
|
||||
file: tarPath,
|
||||
prefix: 'project',
|
||||
gzip: true,
|
||||
portable: true,
|
||||
},
|
||||
['.']
|
||||
);
|
||||
} catch (err) {
|
||||
clearTimeout(timer);
|
||||
if (spinner.isSpinning) {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
confirmRecentConvexInviteAsync,
|
||||
formatConvexProject,
|
||||
formatConvexTeam,
|
||||
formatConvexTeamConnection,
|
||||
getConvexProjectDashboardUrl,
|
||||
getConvexTeamDashboardUrl,
|
||||
} from '../convex';
|
||||
import {
|
||||
ConvexProjectData,
|
||||
ConvexTeamConnectionData,
|
||||
} from '../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../log';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
|
||||
jest.mock('../../log');
|
||||
jest.mock('../../prompts');
|
||||
|
||||
describe('Convex command utilities', () => {
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: '2024-01-02T00:00:00.000Z',
|
||||
invitedEmail: '[email protected]',
|
||||
};
|
||||
|
||||
const mockProject: ConvexProjectData = {
|
||||
id: 'convex-project-1',
|
||||
convexProjectIdentifier: 'project-123',
|
||||
convexProjectName: 'Test Project',
|
||||
convexProjectSlug: 'test project',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
convexTeamConnection: mockConnection,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('builds encoded Convex dashboard URLs', () => {
|
||||
expect(getConvexTeamDashboardUrl(mockConnection)).toBe(
|
||||
'https://dashboard.convex.dev/t/test%20team'
|
||||
);
|
||||
expect(getConvexProjectDashboardUrl(mockProject)).toBe(
|
||||
'https://dashboard.convex.dev/t/test%20team/test%20project'
|
||||
);
|
||||
});
|
||||
|
||||
it('formats team and project metadata with invite details', () => {
|
||||
expect(formatConvexTeam(mockConnection)).toBe('Test Team / test team');
|
||||
expect(formatConvexTeamConnection(mockConnection)).toContain('Test Team / test team');
|
||||
expect(formatConvexTeamConnection(mockConnection)).not.toContain('team-123');
|
||||
expect(formatConvexTeamConnection(mockConnection)).not.toContain('connection-1');
|
||||
expect(formatConvexTeamConnection(mockConnection)).toContain('[email protected]');
|
||||
expect(formatConvexTeamConnection(mockConnection)).toContain('2024-01-02T00:00:00.000Z');
|
||||
expect(formatConvexProject(mockProject)).toContain('project-123');
|
||||
expect(formatConvexProject(mockProject)).not.toContain('convex-project-1');
|
||||
expect(formatConvexProject(mockProject)).toContain('Test Team / test team');
|
||||
expect(formatConvexProject(mockProject)).not.toContain('team-123');
|
||||
});
|
||||
|
||||
it('does not prompt when the previous invite timestamp is invalid', async () => {
|
||||
await expect(
|
||||
confirmRecentConvexInviteAsync(
|
||||
{ ...mockConnection, invitedAt: 'not-a-date' },
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not prompt when the previous invite is old', async () => {
|
||||
await expect(
|
||||
confirmRecentConvexInviteAsync(
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('prompts before resending a recent invite in interactive mode', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
|
||||
await expect(
|
||||
confirmRecentConvexInviteAsync(
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{ nonInteractive: false }
|
||||
)
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('Are you sure you want to send another invite?'),
|
||||
});
|
||||
});
|
||||
|
||||
it('warns and proceeds for recent invites in non-interactive mode', async () => {
|
||||
await expect(
|
||||
confirmRecentConvexInviteAsync(
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{ nonInteractive: true }
|
||||
)
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(expect.stringContaining('non-interactive mode'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { ConvexProjectData, ConvexTeamConnectionData } from '../graphql/types/ConvexTeamConnection';
|
||||
import Log, { link } from '../log';
|
||||
import { confirmAsync } from '../prompts';
|
||||
|
||||
const CONVEX_DASHBOARD_HOST = 'https://dashboard.convex.dev';
|
||||
const RECENT_INVITE_THRESHOLD_MS = 60 * 60 * 1000;
|
||||
|
||||
export function getConvexTeamDashboardUrl(connection: ConvexTeamConnectionData): string {
|
||||
return `${CONVEX_DASHBOARD_HOST}/t/${encodeURIComponent(connection.convexTeamSlug)}`;
|
||||
}
|
||||
|
||||
export function getConvexProjectDashboardUrl(project: ConvexProjectData): string {
|
||||
return `${getConvexTeamDashboardUrl(project.convexTeamConnection)}/${encodeURIComponent(
|
||||
project.convexProjectSlug
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function formatConvexTeam(connection: ConvexTeamConnectionData): string {
|
||||
return `${connection.convexTeamName} / ${connection.convexTeamSlug}`;
|
||||
}
|
||||
|
||||
export function formatConvexTeamConnection(connection: ConvexTeamConnectionData): string {
|
||||
const lines = [
|
||||
`${chalk.bold('Team')}: ${formatConvexTeam(connection)}`,
|
||||
`${chalk.bold('Dashboard')}: ${link(getConvexTeamDashboardUrl(connection), { dim: false })}`,
|
||||
];
|
||||
|
||||
if (connection.invitedEmail) {
|
||||
lines.push(`${chalk.bold('Invited email')}: ${connection.invitedEmail}`);
|
||||
}
|
||||
if (connection.invitedAt) {
|
||||
lines.push(`${chalk.bold('Invited at')}: ${connection.invitedAt}`);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatConvexProject(project: ConvexProjectData): string {
|
||||
return [
|
||||
`${chalk.bold('Name')}: ${project.convexProjectName}`,
|
||||
`${chalk.bold('Slug')}: ${project.convexProjectSlug}`,
|
||||
`${chalk.bold('Identifier')}: ${project.convexProjectIdentifier}`,
|
||||
`${chalk.bold('Team')}: ${formatConvexTeam(project.convexTeamConnection)}`,
|
||||
`${chalk.bold('Dashboard')}: ${link(getConvexProjectDashboardUrl(project), { dim: false })}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function logNoConvexTeams(accountName: string): void {
|
||||
Log.warn(`No Convex team is linked to account ${chalk.bold(accountName)} on EAS.`);
|
||||
}
|
||||
|
||||
export function logNoConvexProject(projectName: string): void {
|
||||
Log.warn(`No Convex project is linked to Expo app ${chalk.bold(projectName)} on EAS.`);
|
||||
}
|
||||
|
||||
export async function confirmRecentConvexInviteAsync(
|
||||
connection: ConvexTeamConnectionData,
|
||||
{ nonInteractive }: { nonInteractive: boolean }
|
||||
): Promise<boolean> {
|
||||
const invitedAt = connection.invitedAt ? new Date(connection.invitedAt) : null;
|
||||
if (!invitedAt || Number.isNaN(invitedAt.getTime())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const timeSinceInviteMs = Date.now() - invitedAt.getTime();
|
||||
if (timeSinceInviteMs >= RECENT_INVITE_THRESHOLD_MS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previousInvite = `A Convex team invite was already sent${connection.invitedEmail ? ` to ${connection.invitedEmail}` : ''} at ${connection.invitedAt}.`;
|
||||
if (nonInteractive) {
|
||||
Log.warn(
|
||||
`${previousInvite} Sending another invite because this command is running in non-interactive mode.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return await confirmAsync({
|
||||
message: `${previousInvite} Are you sure you want to send another invite?`,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import {
|
||||
ConvexTeamConnectionData,
|
||||
SetupConvexProjectResultData,
|
||||
} from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import { confirmAsync, promptAsync, selectAsync } from '../../../../prompts';
|
||||
import { Actor } from '../../../../user/User';
|
||||
import IntegrationsConvexConnect from '../connect';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../graphql/mutations/ConvexMutation');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('@expo/spawn-async');
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe(IntegrationsConvexConnect, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
const testProjectDir = '/test/project';
|
||||
const testAccountId = 'test-account-id';
|
||||
const testAccountName = 'testuser';
|
||||
|
||||
const mockActor: Actor = {
|
||||
__typename: 'User',
|
||||
id: 'test-user-id',
|
||||
username: testAccountName,
|
||||
email: '[email protected]',
|
||||
featureGates: {},
|
||||
isExpoAdmin: false,
|
||||
primaryAccount: {
|
||||
id: testAccountId,
|
||||
name: testAccountName,
|
||||
ownerUserActor: null,
|
||||
users: [],
|
||||
},
|
||||
preferences: { onboarding: null },
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
const mockRobotActor: Actor = {
|
||||
__typename: 'Robot',
|
||||
id: 'robot-1',
|
||||
featureGates: {},
|
||||
isExpoAdmin: false,
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
const mockAccount = {
|
||||
id: testAccountId,
|
||||
name: testAccountName,
|
||||
ownerUserActor: { id: 'test-user-id', username: testAccountName },
|
||||
users: [{ role: 'OWNER' as any, actor: { id: 'test-user-id' } }],
|
||||
};
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: null,
|
||||
invitedEmail: null,
|
||||
};
|
||||
|
||||
const mockSetupResult: SetupConvexProjectResultData = {
|
||||
convexDeploymentName: 'happy-otter-123',
|
||||
convexDeploymentUrl: 'https://happy-otter-123.convex.cloud',
|
||||
deployKey: 'dev:happy-otter-123|abc123token',
|
||||
convexProject: {
|
||||
id: 'convex-project-1',
|
||||
convexProjectIdentifier: 'project-123',
|
||||
convexProjectName: 'testapp',
|
||||
convexProjectSlug: 'testapp',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
convexTeamConnection: mockConnection,
|
||||
},
|
||||
};
|
||||
|
||||
function createCommand(argv: string[], actor: Actor = mockActor): IntegrationsConvexConnect {
|
||||
const command = new IntegrationsConvexConnect(argv, mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
exp: { name: 'testapp', slug: 'testapp' },
|
||||
projectDir: testProjectDir,
|
||||
},
|
||||
loggedIn: { graphqlClient, actor },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'withTick').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'addNewLineIfNone').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'newLine').mockImplementation(() => {});
|
||||
|
||||
jest.mocked(fs.pathExists).mockResolvedValue(false as never);
|
||||
jest.mocked(fs.writeFile).mockResolvedValue(undefined as never);
|
||||
jest.mocked(fs.readFile).mockResolvedValue('' as never);
|
||||
jest.mocked(spawnAsync).mockResolvedValue({} as never);
|
||||
|
||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockAccount as any);
|
||||
jest.mocked(selectAsync).mockResolvedValue('aws-us-east-1');
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
jest.mocked(promptAsync).mockImplementation(async (params: any) => ({
|
||||
[params.name]: params.initial,
|
||||
}));
|
||||
jest.mocked(ConvexMutation.createConvexTeamConnectionAsync).mockResolvedValue(mockConnection);
|
||||
jest.mocked(ConvexMutation.setupConvexProjectAsync).mockResolvedValue(mockSetupResult);
|
||||
jest.mocked(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('creates a Convex team and project with the new mutation shape', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(ConvexMutation.createConvexTeamConnectionAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
accountId: testAccountId,
|
||||
deploymentRegion: 'aws-us-east-1',
|
||||
convexTeamName: testAccountName,
|
||||
});
|
||||
expect(ConvexMutation.setupConvexProjectAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: testProjectId,
|
||||
convexTeamConnectionId: 'connection-1',
|
||||
deploymentRegion: 'aws-us-east-1',
|
||||
projectName: 'testapp',
|
||||
});
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{ convexTeamConnectionId: 'connection-1' }
|
||||
);
|
||||
expect(spawnAsync).toHaveBeenCalledWith('npx', ['expo', 'install', 'convex'], {
|
||||
cwd: testProjectDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses an existing Convex team connection', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
|
||||
await createCommand(['--region', 'aws-eu-west-1']).runAsync();
|
||||
|
||||
expect(ConvexMutation.createConvexTeamConnectionAsync).not.toHaveBeenCalled();
|
||||
expect(ConvexMutation.setupConvexProjectAsync).toHaveBeenCalledWith(graphqlClient, {
|
||||
appId: testProjectId,
|
||||
convexTeamConnectionId: 'connection-1',
|
||||
deploymentRegion: 'aws-eu-west-1',
|
||||
projectName: 'testapp',
|
||||
});
|
||||
expect(spawnAsync).toHaveBeenCalledWith('npx', ['expo', 'install', 'convex'], {
|
||||
cwd: testProjectDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not create a Convex team if installing the Convex package fails first', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([]);
|
||||
jest.mocked(spawnAsync).mockRejectedValue(new Error('install failed') as never);
|
||||
|
||||
await expect(createCommand([]).runAsync()).rejects.toThrow('install failed');
|
||||
|
||||
expect(ConvexMutation.createConvexTeamConnectionAsync).not.toHaveBeenCalled();
|
||||
expect(ConvexMutation.setupConvexProjectAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips the verified email invite when the actor has no email', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
|
||||
await createCommand([], mockRobotActor).runAsync();
|
||||
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not determine your verified email address')
|
||||
);
|
||||
expect(Log.warn).not.toHaveBeenCalledWith(expect.stringContaining('Convex dashboard'));
|
||||
});
|
||||
|
||||
it('logs the error when sending a verified email invite fails', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
jest
|
||||
.mocked(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync)
|
||||
.mockRejectedValue(new Error('Convex invite service unavailable'));
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to send Convex team invitation to [email protected]')
|
||||
);
|
||||
expect(Log.warn).toHaveBeenCalledWith('Convex invite service unavailable');
|
||||
expect(Log.warn).not.toHaveBeenCalledWith(expect.stringContaining('Convex dashboard'));
|
||||
});
|
||||
|
||||
it('asks before resending a recent team invite for an existing connection', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
invitedEmail: '[email protected]',
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('Are you sure you want to send another invite?'),
|
||||
});
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith('Skipped sending Convex team invitation.');
|
||||
});
|
||||
|
||||
it('writes the deploy key and Convex URL to .env.local', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
const envPath = `${testProjectDir}/.env.local`;
|
||||
const writeCall = jest.mocked(fs.writeFile).mock.calls.find(call => call[0] === envPath);
|
||||
expect(writeCall?.[1]).toContain('CONVEX_DEPLOY_KEY=dev:happy-otter-123|abc123token');
|
||||
expect(writeCall?.[1]).toContain('EXPO_PUBLIC_CONVEX_URL=https://happy-otter-123.convex.cloud');
|
||||
});
|
||||
|
||||
it('prompts before overwriting existing Convex .env.local values', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
jest.mocked(fs.pathExists).mockResolvedValue(true as never);
|
||||
jest.mocked(fs.readFile).mockResolvedValue('CONVEX_DEPLOY_KEY=old-key\n' as never);
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('already contains CONVEX_DEPLOY_KEY'),
|
||||
});
|
||||
});
|
||||
|
||||
it('uses defaults in non-interactive mode', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([]);
|
||||
|
||||
await createCommand(['--non-interactive']).runAsync();
|
||||
|
||||
expect(selectAsync).not.toHaveBeenCalled();
|
||||
expect(promptAsync).not.toHaveBeenCalled();
|
||||
expect(ConvexMutation.createConvexTeamConnectionAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
expect.objectContaining({
|
||||
deploymentRegion: 'aws-us-east-1',
|
||||
convexTeamName: testAccountName,
|
||||
})
|
||||
);
|
||||
expect(ConvexMutation.setupConvexProjectAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
expect.objectContaining({
|
||||
appId: testProjectId,
|
||||
deploymentRegion: 'aws-us-east-1',
|
||||
projectName: 'testapp',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import openBrowserAsync from 'better-opn';
|
||||
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import {
|
||||
ConvexProjectData,
|
||||
ConvexTeamConnectionData,
|
||||
} from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { ora } from '../../../../ora';
|
||||
import IntegrationsConvexDashboard from '../dashboard';
|
||||
|
||||
jest.mock('better-opn');
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: jest.fn(() => ({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe(IntegrationsConvexDashboard, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: null,
|
||||
invitedEmail: null,
|
||||
};
|
||||
|
||||
const mockProject: ConvexProjectData = {
|
||||
id: 'convex-project-1',
|
||||
convexProjectIdentifier: 'project-123',
|
||||
convexProjectName: 'Test Project',
|
||||
convexProjectSlug: 'test project',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
convexTeamConnection: mockConnection,
|
||||
};
|
||||
|
||||
function createCommand(): IntegrationsConvexDashboard {
|
||||
const command = new IntegrationsConvexDashboard([], mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
exp: { slug: 'testapp' },
|
||||
},
|
||||
loggedIn: { graphqlClient },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(mockProject);
|
||||
jest.mocked(openBrowserAsync).mockResolvedValue({} as never);
|
||||
jest.mocked(ora).mockReturnValue({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('opens the linked Convex project dashboard', async () => {
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(openBrowserAsync).toHaveBeenCalledWith(
|
||||
'https://dashboard.convex.dev/t/test%20team/test%20project'
|
||||
);
|
||||
});
|
||||
|
||||
it('logs an empty state when no project link exists', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(null);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(openBrowserAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No Convex project is linked to Expo app')
|
||||
);
|
||||
});
|
||||
|
||||
it('fails the spinner when the browser cannot be opened', async () => {
|
||||
jest.mocked(openBrowserAsync).mockResolvedValue(false);
|
||||
const spinner = {
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
};
|
||||
jest.mocked(ora).mockReturnValue(spinner as any);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(spinner.fail).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Unable to open a web browser')
|
||||
);
|
||||
});
|
||||
|
||||
it('fails the spinner and rethrows when opening the browser throws', async () => {
|
||||
jest.mocked(openBrowserAsync).mockRejectedValue(new Error('open failed'));
|
||||
const spinner = {
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
};
|
||||
jest.mocked(ora).mockReturnValue(spinner as any);
|
||||
|
||||
await expect(createCommand().runAsync()).rejects.toThrow('open failed');
|
||||
|
||||
expect(spinner.fail).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Unable to open a web browser')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import {
|
||||
ConvexProjectData,
|
||||
ConvexTeamConnectionData,
|
||||
} from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import IntegrationsConvexProjectDelete from '../project/delete';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../graphql/mutations/ConvexMutation');
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe(IntegrationsConvexProjectDelete, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: null,
|
||||
invitedEmail: null,
|
||||
};
|
||||
|
||||
const mockProject: ConvexProjectData = {
|
||||
id: 'convex-project-1',
|
||||
convexProjectIdentifier: 'project-123',
|
||||
convexProjectName: 'Test Project',
|
||||
convexProjectSlug: 'test-project',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
convexTeamConnection: mockConnection,
|
||||
};
|
||||
|
||||
function createCommand(argv: string[]): IntegrationsConvexProjectDelete {
|
||||
const command = new IntegrationsConvexProjectDelete(argv, mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
exp: { slug: 'testapp' },
|
||||
},
|
||||
loggedIn: { graphqlClient },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'addNewLineIfNone').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'newLine').mockImplementation(() => {});
|
||||
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(mockProject);
|
||||
jest.mocked(ConvexMutation.deleteConvexProjectAsync).mockResolvedValue('convex-project-1');
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('deletes the linked Convex project after confirmation', async () => {
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('This does not destroy resources on Convex'),
|
||||
});
|
||||
expect(ConvexMutation.deleteConvexProjectAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'convex-project-1'
|
||||
);
|
||||
});
|
||||
|
||||
it('skips deletion when the user cancels', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(ConvexMutation.deleteConvexProjectAsync).not.toHaveBeenCalled();
|
||||
expect(Log.error).toHaveBeenCalledWith('Canceled deletion of the Convex project link');
|
||||
});
|
||||
|
||||
it('uses --yes to skip the confirmation prompt', async () => {
|
||||
await createCommand(['--yes']).runAsync();
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('This does not destroy resources on Convex')
|
||||
);
|
||||
expect(ConvexMutation.deleteConvexProjectAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'convex-project-1'
|
||||
);
|
||||
});
|
||||
|
||||
it('logs an empty state when no project link exists', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(null);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No Convex project is linked to Expo app')
|
||||
);
|
||||
expect(ConvexMutation.deleteConvexProjectAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import {
|
||||
ConvexProjectData,
|
||||
ConvexTeamConnectionData,
|
||||
} from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import IntegrationsConvexProject from '../project';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../log');
|
||||
|
||||
describe(IntegrationsConvexProject, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: null,
|
||||
invitedEmail: null,
|
||||
};
|
||||
|
||||
const mockProject: ConvexProjectData = {
|
||||
id: 'convex-project-1',
|
||||
convexProjectIdentifier: 'project-123',
|
||||
convexProjectName: 'Test Project',
|
||||
convexProjectSlug: 'test-project',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
convexTeamConnection: mockConnection,
|
||||
};
|
||||
|
||||
function createCommand(): IntegrationsConvexProject {
|
||||
const command = new IntegrationsConvexProject([], mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
exp: { slug: 'testapp' },
|
||||
},
|
||||
loggedIn: { graphqlClient },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('prints linked Convex project metadata', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(mockProject);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Convex project linked to testapp')
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('project-123'));
|
||||
});
|
||||
|
||||
it('prints an empty state when no Convex project is linked', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexProjectByAppIdAsync).mockResolvedValue(null);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No Convex project is linked to Expo app')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import { confirmAsync, selectAsync } from '../../../../prompts';
|
||||
import IntegrationsConvexTeamDelete from '../team/delete';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../graphql/mutations/ConvexMutation');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe(IntegrationsConvexTeamDelete, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
const testAccountId = 'test-account-id';
|
||||
const testAccountName = 'testuser';
|
||||
|
||||
const mockAccount = {
|
||||
id: testAccountId,
|
||||
name: testAccountName,
|
||||
ownerUserActor: { id: 'test-user-id', username: testAccountName },
|
||||
users: [{ role: 'OWNER' as any, actor: { id: 'test-user-id' } }],
|
||||
};
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: '2024-01-02T00:00:00.000Z',
|
||||
invitedEmail: '[email protected]',
|
||||
};
|
||||
|
||||
function createCommand(argv: string[]): IntegrationsConvexTeamDelete {
|
||||
const command = new IntegrationsConvexTeamDelete(argv, mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
},
|
||||
loggedIn: { graphqlClient },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'error').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'addNewLineIfNone').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'newLine').mockImplementation(() => {});
|
||||
|
||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockAccount as any);
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
jest.mocked(ConvexMutation.deleteConvexTeamConnectionAsync).mockResolvedValue(mockConnection);
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('deletes the selected Convex team link after confirmation', async () => {
|
||||
await createCommand(['test-team']).runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('This does not destroy resources on Convex'),
|
||||
});
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'connection-1'
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Test Team / test-team'));
|
||||
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('team-123'));
|
||||
});
|
||||
|
||||
it('skips deletion when the user cancels', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
|
||||
await createCommand(['test-team']).runAsync();
|
||||
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).not.toHaveBeenCalled();
|
||||
expect(Log.error).toHaveBeenCalledWith('Canceled deletion of the Convex team link');
|
||||
});
|
||||
|
||||
it('uses --yes to skip the confirmation prompt', async () => {
|
||||
await createCommand(['test-team', '--yes']).runAsync();
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('This does not destroy resources on Convex')
|
||||
);
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'connection-1'
|
||||
);
|
||||
});
|
||||
|
||||
it('logs an empty state when no team links exist', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(Log.warn).toHaveBeenCalledWith(expect.stringContaining('No Convex team is linked'));
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws when the provided team is not linked to the account', async () => {
|
||||
await expect(createCommand(['missing-team']).runAsync()).rejects.toThrow(
|
||||
'Convex team missing-team is not linked to this account.'
|
||||
);
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resolves a team by slug when multiple links exist', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
mockConnection,
|
||||
{
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamIdentifier: 'team-456',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand(['other-team', '--non-interactive', '--yes']).runAsync();
|
||||
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'connection-2'
|
||||
);
|
||||
});
|
||||
|
||||
it('requires a team slug in non-interactive mode when multiple links exist', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
mockConnection,
|
||||
{
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(createCommand(['--non-interactive']).runAsync()).rejects.toThrow(
|
||||
'Convex team slug must be provided'
|
||||
);
|
||||
});
|
||||
|
||||
it('prompts to select a team link when multiple links exist in interactive mode', async () => {
|
||||
const otherConnection = {
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
};
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection, otherConnection]);
|
||||
jest.mocked(selectAsync).mockResolvedValue(otherConnection);
|
||||
|
||||
await createCommand(['--yes']).runAsync();
|
||||
|
||||
expect(selectAsync).toHaveBeenCalledWith(
|
||||
'Select a Convex team link to remove',
|
||||
expect.any(Array)
|
||||
);
|
||||
expect(ConvexMutation.deleteConvexTeamConnectionAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
'connection-2'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import { confirmAsync, selectAsync } from '../../../../prompts';
|
||||
import IntegrationsConvexTeamInvite from '../team/invite';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../graphql/mutations/ConvexMutation');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../ora', () => ({
|
||||
ora: () => ({
|
||||
start: jest.fn().mockReturnThis(),
|
||||
succeed: jest.fn().mockReturnThis(),
|
||||
fail: jest.fn().mockReturnThis(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe(IntegrationsConvexTeamInvite, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
const testAccountId = 'test-account-id';
|
||||
const testAccountName = 'testuser';
|
||||
|
||||
const mockActor = {
|
||||
__typename: 'User',
|
||||
id: 'test-user-id',
|
||||
email: '[email protected]',
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
const mockAccount = {
|
||||
id: testAccountId,
|
||||
name: testAccountName,
|
||||
ownerUserActor: { id: 'test-user-id', username: testAccountName },
|
||||
users: [{ role: 'OWNER' as any, actor: { id: 'test-user-id' } }],
|
||||
};
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: '2024-01-02T00:00:00.000Z',
|
||||
invitedEmail: '[email protected]',
|
||||
};
|
||||
|
||||
function createCommand(
|
||||
argv: string[],
|
||||
actor: { __typename: string; [key: string]: any } = mockActor
|
||||
): IntegrationsConvexTeamInvite {
|
||||
const command = new IntegrationsConvexTeamInvite(argv, mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
},
|
||||
loggedIn: { graphqlClient, actor },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'addNewLineIfNone').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'newLine').mockImplementation(() => {});
|
||||
|
||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockAccount as any);
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
jest.mocked(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).mockResolvedValue(true);
|
||||
jest.mocked(confirmAsync).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('sends an invite for the linked Convex team', async () => {
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{ convexTeamConnectionId: 'connection-1' }
|
||||
);
|
||||
});
|
||||
|
||||
it('prints previous invite metadata when it exists', async () => {
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Test Team / test-team'));
|
||||
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('team-123'));
|
||||
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('connection-1'));
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Previous invite'));
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('[email protected]'));
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('2024-01-02T00:00:00.000Z'));
|
||||
});
|
||||
|
||||
it('prompts before resending a recent invite', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(confirmAsync).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('Are you sure you want to send another invite?'),
|
||||
});
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips resending a recent invite when the user does not confirm', async () => {
|
||||
jest.mocked(confirmAsync).mockResolvedValue(false);
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith('Skipped sending Convex team invitation.');
|
||||
});
|
||||
|
||||
it('resends recent invites in non-interactive mode with a warning', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
{
|
||||
...mockConnection,
|
||||
invitedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand(['--non-interactive']).runAsync();
|
||||
|
||||
expect(confirmAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(expect.stringContaining('non-interactive mode'));
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips the invite when the actor has no email', async () => {
|
||||
await createCommand([], { __typename: 'Robot', id: 'robot-1' }).runAsync();
|
||||
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).not.toHaveBeenCalled();
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not determine your verified email address')
|
||||
);
|
||||
expect(Log.warn).not.toHaveBeenCalledWith(expect.stringContaining('Convex dashboard'));
|
||||
});
|
||||
|
||||
it('selects a team link when multiple are available', async () => {
|
||||
const otherConnection = {
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
};
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection, otherConnection]);
|
||||
jest.mocked(selectAsync).mockResolvedValue(otherConnection);
|
||||
|
||||
await createCommand([]).runAsync();
|
||||
|
||||
expect(selectAsync).toHaveBeenCalledWith(
|
||||
'Select a Convex team link to invite yourself to',
|
||||
expect.any(Array)
|
||||
);
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{ convexTeamConnectionId: 'connection-2' }
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves a team by slug when multiple links exist', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
mockConnection,
|
||||
{
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamIdentifier: 'team-456',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
},
|
||||
]);
|
||||
|
||||
await createCommand(['other-team', '--non-interactive']).runAsync();
|
||||
|
||||
expect(ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync).toHaveBeenCalledWith(
|
||||
graphqlClient,
|
||||
{ convexTeamConnectionId: 'connection-2' }
|
||||
);
|
||||
});
|
||||
|
||||
it('requires a team slug in non-interactive mode when multiple links exist', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([
|
||||
mockConnection,
|
||||
{
|
||||
...mockConnection,
|
||||
id: 'connection-2',
|
||||
convexTeamName: 'Other Team',
|
||||
convexTeamSlug: 'other-team',
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(createCommand(['--non-interactive']).runAsync()).rejects.toThrow(
|
||||
'Convex team slug must be provided'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import IntegrationsConvexTeam from '../team';
|
||||
|
||||
jest.mock('../../../../graphql/queries/ConvexQuery');
|
||||
jest.mock('../../../../project/projectUtils');
|
||||
jest.mock('../../../../log');
|
||||
|
||||
describe(IntegrationsConvexTeam, () => {
|
||||
const graphqlClient = {} as ExpoGraphqlClient;
|
||||
const mockConfig = getMockOclifConfig();
|
||||
const testAccountId = 'test-account-id';
|
||||
const testAccountName = 'testuser';
|
||||
|
||||
const mockAccount = {
|
||||
id: testAccountId,
|
||||
name: testAccountName,
|
||||
ownerUserActor: { id: 'test-user-id', username: testAccountName },
|
||||
users: [{ role: 'OWNER' as any, actor: { id: 'test-user-id' } }],
|
||||
};
|
||||
|
||||
const mockConnection: ConvexTeamConnectionData = {
|
||||
id: 'connection-1',
|
||||
convexTeamIdentifier: 'team-123',
|
||||
convexTeamName: 'Test Team',
|
||||
convexTeamSlug: 'test-team',
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
updatedAt: '2024-01-01T00:00:00.000Z',
|
||||
invitedAt: null,
|
||||
invitedEmail: null,
|
||||
};
|
||||
|
||||
function createCommand(): IntegrationsConvexTeam {
|
||||
const command = new IntegrationsConvexTeam([], mockConfig);
|
||||
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
|
||||
privateProjectConfig: {
|
||||
projectId: testProjectId,
|
||||
},
|
||||
loggedIn: { graphqlClient },
|
||||
} as never);
|
||||
return command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(Log, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'warn').mockImplementation(() => {});
|
||||
jest.spyOn(Log, 'newLine').mockImplementation(() => {});
|
||||
jest.mocked(getOwnerAccountForProjectIdAsync).mockResolvedValue(mockAccount as any);
|
||||
});
|
||||
|
||||
it('prints linked Convex team metadata', async () => {
|
||||
jest
|
||||
.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync)
|
||||
.mockResolvedValue([mockConnection]);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(Log.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Convex teams linked to @testuser')
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('Test Team / test-team'));
|
||||
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('team-123'));
|
||||
});
|
||||
|
||||
it('prints an empty state when no Convex teams are linked', async () => {
|
||||
jest.mocked(ConvexQuery.getConvexTeamConnectionsByAccountIdAsync).mockResolvedValue([]);
|
||||
|
||||
await createCommand().runAsync();
|
||||
|
||||
expect(Log.warn).toHaveBeenCalledWith(expect.stringContaining('No Convex team is linked'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,350 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import dotenv from 'dotenv';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { confirmRecentConvexInviteAsync, formatConvexTeam } from '../../../commandUtils/convex';
|
||||
import { EASNonInteractiveFlag } from '../../../commandUtils/flags';
|
||||
import { ConvexMutation } from '../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../graphql/types/ConvexTeamConnection';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils';
|
||||
import { confirmAsync, promptAsync, selectAsync } from '../../../prompts';
|
||||
import { Actor } from '../../../user/User';
|
||||
|
||||
const CONVEX_REGIONS = [
|
||||
{ title: 'US East (aws-us-east-1)', value: 'aws-us-east-1' },
|
||||
{ title: 'EU West (aws-eu-west-1)', value: 'aws-eu-west-1' },
|
||||
];
|
||||
|
||||
const DEFAULT_REGION = 'aws-us-east-1';
|
||||
|
||||
export default class IntegrationsConvexConnect extends EasCommand {
|
||||
static override description = 'connect Convex to your Expo project';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
region: Flags.string({
|
||||
description: 'Convex deployment region (e.g. aws-us-east-1, aws-eu-west-1)',
|
||||
options: CONVEX_REGIONS.map(r => r.value),
|
||||
}),
|
||||
'team-name': Flags.string({
|
||||
description: 'Name for the new Convex team (defaults to EAS account name)',
|
||||
}),
|
||||
'project-name': Flags.string({
|
||||
description: 'Name for the Convex project (defaults to app slug)',
|
||||
}),
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags: {
|
||||
region: regionFlag,
|
||||
'team-name': teamNameFlag,
|
||||
'project-name': projectNameFlag,
|
||||
'non-interactive': nonInteractive,
|
||||
},
|
||||
} = await this.parse(IntegrationsConvexConnect);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId, exp, projectDir },
|
||||
loggedIn: { graphqlClient, actor },
|
||||
} = await this.getContextAsync(IntegrationsConvexConnect, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
|
||||
// 1. Check for existing Convex team connections
|
||||
const existingConnections = await ConvexQuery.getConvexTeamConnectionsByAccountIdAsync(
|
||||
graphqlClient,
|
||||
account.id
|
||||
);
|
||||
|
||||
const region = await this.resolveRegionAsync(regionFlag, nonInteractive);
|
||||
let connection: ConvexTeamConnectionData | null = null;
|
||||
let teamName: string | null = null;
|
||||
|
||||
if (existingConnections.length === 0) {
|
||||
// 2a. No connection - resolve a team name and create it after local package install succeeds
|
||||
teamName = await this.resolveTeamNameAsync(teamNameFlag, account.name, nonInteractive);
|
||||
} else if (existingConnections.length === 1) {
|
||||
// 2b. Single existing connection
|
||||
connection = existingConnections[0];
|
||||
Log.withTick(`Using existing Convex team ${chalk.bold(formatConvexTeam(connection))}`);
|
||||
} else {
|
||||
// 2c. Multiple connections - prompt to select
|
||||
connection = await this.selectConnectionAsync(existingConnections, nonInteractive);
|
||||
}
|
||||
|
||||
// 3. Resolve project name before project setup mutation
|
||||
const projectName = await this.resolveProjectNameAsync(
|
||||
projectNameFlag,
|
||||
exp.slug,
|
||||
nonInteractive
|
||||
);
|
||||
|
||||
// 4. Install the Convex package before creating new server-side resources
|
||||
await this.installConvexPackageAsync(projectDir);
|
||||
|
||||
if (!connection) {
|
||||
const spinner = ora('Creating Convex team').start();
|
||||
try {
|
||||
connection = await ConvexMutation.createConvexTeamConnectionAsync(graphqlClient, {
|
||||
accountId: account.id,
|
||||
deploymentRegion: region,
|
||||
convexTeamName: teamName ?? account.name,
|
||||
});
|
||||
spinner.succeed(`Created Convex team ${chalk.bold(formatConvexTeam(connection))}`);
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to create Convex team');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Set up Convex project
|
||||
const spinner = ora('Setting up Convex project').start();
|
||||
let setupResult;
|
||||
try {
|
||||
setupResult = await ConvexMutation.setupConvexProjectAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
convexTeamConnectionId: connection.id,
|
||||
deploymentRegion: region,
|
||||
projectName,
|
||||
});
|
||||
spinner.succeed(
|
||||
`Created Convex project ${chalk.bold(projectName)} with deployment ${chalk.bold(setupResult.convexDeploymentName)}`
|
||||
);
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to set up Convex project');
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 6. Send team invite (non-fatal)
|
||||
await this.sendTeamInviteAsync(graphqlClient, connection, actor, { nonInteractive });
|
||||
|
||||
// 7. Write deploy key and URL to .env.local
|
||||
await this.writeEnvLocalAsync(
|
||||
projectDir,
|
||||
setupResult.deployKey,
|
||||
setupResult.convexDeploymentUrl,
|
||||
nonInteractive
|
||||
);
|
||||
|
||||
// 8. Success message
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.green('Convex is ready!'));
|
||||
Log.newLine();
|
||||
Log.log('Next steps:');
|
||||
Log.log(` 1. Start the Convex dev server: ${chalk.cyan('npx convex dev')}`);
|
||||
Log.newLine();
|
||||
if (this.getActorEmail(actor)) {
|
||||
Log.log(
|
||||
`Check your email for an invitation to join your Convex team. Accept it for full dashboard access.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveRegionAsync(
|
||||
flagValue: string | undefined,
|
||||
nonInteractive: boolean
|
||||
): Promise<string> {
|
||||
if (flagValue) {
|
||||
return flagValue;
|
||||
}
|
||||
if (nonInteractive) {
|
||||
return DEFAULT_REGION;
|
||||
}
|
||||
return await selectAsync('Select a Convex deployment region', CONVEX_REGIONS);
|
||||
}
|
||||
|
||||
private async resolveTeamNameAsync(
|
||||
flagValue: string | undefined,
|
||||
accountName: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<string> {
|
||||
if (flagValue) {
|
||||
return flagValue;
|
||||
}
|
||||
if (nonInteractive) {
|
||||
return accountName;
|
||||
}
|
||||
const { teamName } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'teamName',
|
||||
message: 'Convex team name',
|
||||
initial: accountName,
|
||||
validate: (value: string) => (value.trim() ? true : 'Team name cannot be empty'),
|
||||
});
|
||||
return teamName;
|
||||
}
|
||||
|
||||
private async resolveProjectNameAsync(
|
||||
flagValue: string | undefined,
|
||||
slug: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<string> {
|
||||
if (flagValue) {
|
||||
return flagValue;
|
||||
}
|
||||
if (nonInteractive) {
|
||||
return slug;
|
||||
}
|
||||
const { projectName } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'projectName',
|
||||
message: 'Convex project name',
|
||||
initial: slug,
|
||||
validate: (value: string) => (value.trim() ? true : 'Project name cannot be empty'),
|
||||
});
|
||||
return projectName;
|
||||
}
|
||||
|
||||
private async selectConnectionAsync(
|
||||
connections: ConvexTeamConnectionData[],
|
||||
nonInteractive: boolean
|
||||
): Promise<ConvexTeamConnectionData> {
|
||||
if (nonInteractive) {
|
||||
return connections[0];
|
||||
}
|
||||
const choices = connections.map(c => ({
|
||||
title: `${formatConvexTeam(c)} (created ${new Date(c.createdAt).toLocaleDateString()})`,
|
||||
value: c,
|
||||
}));
|
||||
return await selectAsync('Select a Convex team connection', choices);
|
||||
}
|
||||
|
||||
private getActorEmail(actor: Actor): string | null {
|
||||
return actor.__typename === 'User' ? actor.email : null;
|
||||
}
|
||||
|
||||
private async sendTeamInviteAsync(
|
||||
graphqlClient: Parameters<typeof ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync>[0],
|
||||
connection: ConvexTeamConnectionData,
|
||||
actor: Actor,
|
||||
{ nonInteractive }: { nonInteractive: boolean }
|
||||
): Promise<void> {
|
||||
const email = this.getActorEmail(actor);
|
||||
if (!email) {
|
||||
Log.warn(
|
||||
`Could not determine your verified email address, so no Convex team invitation was sent. Run ${chalk.cyan(
|
||||
'eas integrations:convex:team:invite'
|
||||
)} after signing in with a user account.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirmRecentConvexInviteAsync(connection, { nonInteractive }))) {
|
||||
Log.warn('Skipped sending Convex team invitation.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync(graphqlClient, {
|
||||
convexTeamConnectionId: connection.id,
|
||||
});
|
||||
Log.withTick(`Sent Convex team invitation to ${chalk.bold(email)}`);
|
||||
} catch (error) {
|
||||
Log.warn(
|
||||
`Failed to send Convex team invitation to ${email}. Run ${chalk.cyan(
|
||||
'eas integrations:convex:team:invite'
|
||||
)} to retry.`
|
||||
);
|
||||
Log.warn(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
private async installConvexPackageAsync(projectDir: string): Promise<void> {
|
||||
Log.newLine();
|
||||
Log.log(`Running ${chalk.bold('npx expo install convex')}`);
|
||||
Log.newLine();
|
||||
|
||||
try {
|
||||
await spawnAsync('npx', ['expo', 'install', 'convex'], {
|
||||
cwd: projectDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
Log.withTick(`Installed the ${chalk.bold('convex')} npm package`);
|
||||
} catch (error) {
|
||||
Log.warn(
|
||||
`Failed to install the ${chalk.bold('convex')} npm package. Run ${chalk.cyan(
|
||||
'npx expo install convex'
|
||||
)} from your project directory, then run ${chalk.cyan('npx convex dev')}.`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async writeEnvLocalAsync(
|
||||
projectDir: string,
|
||||
deployKey: string,
|
||||
convexUrl: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
const envPath = path.join(projectDir, '.env.local');
|
||||
let existingContent: Record<string, string> = {};
|
||||
let rawContent = '';
|
||||
|
||||
if (await fs.pathExists(envPath)) {
|
||||
rawContent = await fs.readFile(envPath, 'utf8');
|
||||
existingContent = dotenv.parse(rawContent);
|
||||
|
||||
if (existingContent.CONVEX_DEPLOY_KEY && !nonInteractive) {
|
||||
const overwrite = await confirmAsync({
|
||||
message: `.env.local already contains CONVEX_DEPLOY_KEY. Overwrite Convex values?`,
|
||||
});
|
||||
if (!overwrite) {
|
||||
Log.log('Skipping .env.local update. Deploy key:');
|
||||
Log.log(` CONVEX_DEPLOY_KEY=${deployKey}`);
|
||||
Log.log(` EXPO_PUBLIC_CONVEX_URL=${convexUrl}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedContent = this.mergeEnvContent(rawContent, {
|
||||
CONVEX_DEPLOY_KEY: deployKey,
|
||||
EXPO_PUBLIC_CONVEX_URL: convexUrl,
|
||||
});
|
||||
|
||||
await fs.writeFile(envPath, updatedContent);
|
||||
Log.withTick(`Wrote Convex config to ${chalk.bold('.env.local')}`);
|
||||
}
|
||||
|
||||
private mergeEnvContent(rawContent: string, newVars: Record<string, string>): string {
|
||||
let content = rawContent;
|
||||
const keysToAdd: Record<string, string> = { ...newVars };
|
||||
|
||||
for (const [key, value] of Object.entries(newVars)) {
|
||||
// Replace existing line if present
|
||||
const regex = new RegExp(`^${key}=.*$`, 'm');
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, `${key}=${value}`);
|
||||
delete keysToAdd[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Append any keys that weren't already in the file
|
||||
const remaining = Object.entries(keysToAdd);
|
||||
if (remaining.length > 0) {
|
||||
if (content.length > 0 && !content.endsWith('\n')) {
|
||||
content += '\n';
|
||||
}
|
||||
for (const [key, value] of remaining) {
|
||||
content += `${key}=${value}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import openBrowserAsync from 'better-opn';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { getConvexProjectDashboardUrl, logNoConvexProject } from '../../../commandUtils/convex';
|
||||
import { ConvexQuery } from '../../../graphql/queries/ConvexQuery';
|
||||
import { ora } from '../../../ora';
|
||||
|
||||
export default class IntegrationsConvexDashboard extends EasCommand {
|
||||
static override description = 'open the Convex dashboard for the linked Convex project';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
privateProjectConfig: { projectId, exp },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(IntegrationsConvexDashboard, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const convexProject = await ConvexQuery.getConvexProjectByAppIdAsync(graphqlClient, projectId);
|
||||
|
||||
if (!convexProject) {
|
||||
logNoConvexProject(exp.slug);
|
||||
return;
|
||||
}
|
||||
|
||||
const dashboardUrl = getConvexProjectDashboardUrl(convexProject);
|
||||
const failedMessage = `Unable to open a web browser. Convex dashboard is available at: ${dashboardUrl}`;
|
||||
const spinner = ora(`Opening ${dashboardUrl}`).start();
|
||||
try {
|
||||
const opened = await openBrowserAsync(dashboardUrl);
|
||||
if (opened) {
|
||||
spinner.succeed(`Opened ${dashboardUrl}`);
|
||||
} else {
|
||||
spinner.fail(failedMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(failedMessage);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { formatConvexProject, logNoConvexProject } from '../../../commandUtils/convex';
|
||||
import { ConvexQuery } from '../../../graphql/queries/ConvexQuery';
|
||||
import Log from '../../../log';
|
||||
|
||||
export default class IntegrationsConvexProject extends EasCommand {
|
||||
static override description = 'display the Convex project linked to the current Expo app';
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
privateProjectConfig: { projectId, exp },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(IntegrationsConvexProject, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const convexProject = await ConvexQuery.getConvexProjectByAppIdAsync(graphqlClient, projectId);
|
||||
|
||||
if (!convexProject) {
|
||||
logNoConvexProject(exp.slug);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log(chalk.bold(`Convex project linked to ${exp.slug}`));
|
||||
Log.log(formatConvexProject(convexProject));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../../commandUtils/EasCommand';
|
||||
import {
|
||||
formatConvexProject,
|
||||
getConvexProjectDashboardUrl,
|
||||
logNoConvexProject,
|
||||
} from '../../../../commandUtils/convex';
|
||||
import { EASNonInteractiveFlag } from '../../../../commandUtils/flags';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import Log, { link } from '../../../../log';
|
||||
import { ora } from '../../../../ora';
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
|
||||
export default class IntegrationsConvexProjectDelete extends EasCommand {
|
||||
static override description =
|
||||
'remove the Convex project link for the current Expo app from EAS servers';
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
yes: Flags.boolean({
|
||||
char: 'y',
|
||||
description: 'Skip confirmation prompt',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
flags: { 'non-interactive': nonInteractive, yes },
|
||||
} = await this.parse(IntegrationsConvexProjectDelete);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId, exp },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(IntegrationsConvexProjectDelete, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const convexProject = await ConvexQuery.getConvexProjectByAppIdAsync(graphqlClient, projectId);
|
||||
|
||||
if (!convexProject) {
|
||||
logNoConvexProject(exp.slug);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(formatConvexProject(convexProject));
|
||||
Log.newLine();
|
||||
|
||||
const dashboardUrl = getConvexProjectDashboardUrl(convexProject);
|
||||
if (!nonInteractive && !yes) {
|
||||
const confirmed = await confirmAsync({
|
||||
message: `Remove this Convex project link from EAS servers? This does not destroy resources on Convex. Convex dashboard: ${link(
|
||||
dashboardUrl,
|
||||
{ dim: false }
|
||||
)}`,
|
||||
});
|
||||
if (!confirmed) {
|
||||
Log.error('Canceled deletion of the Convex project link');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
`Removing the Convex project link from EAS servers. This does not destroy resources on Convex: ${dashboardUrl}`
|
||||
);
|
||||
}
|
||||
|
||||
const spinner = ora('Removing Convex project link').start();
|
||||
try {
|
||||
await ConvexMutation.deleteConvexProjectAsync(graphqlClient, convexProject.id);
|
||||
spinner.succeed(
|
||||
`Removed Convex project ${chalk.bold(convexProject.convexProjectName)} from EAS servers`
|
||||
);
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to remove Convex project link');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { formatConvexTeamConnection, logNoConvexTeams } from '../../../commandUtils/convex';
|
||||
import { ConvexQuery } from '../../../graphql/queries/ConvexQuery';
|
||||
import Log from '../../../log';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils';
|
||||
|
||||
export default class IntegrationsConvexTeam extends EasCommand {
|
||||
static override description =
|
||||
"display Convex teams linked to the current Expo app's owner account";
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(IntegrationsConvexTeam, {
|
||||
nonInteractive: false,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const connections = await ConvexQuery.getConvexTeamConnectionsByAccountIdAsync(
|
||||
graphqlClient,
|
||||
account.id
|
||||
);
|
||||
|
||||
if (connections.length === 0) {
|
||||
logNoConvexTeams(account.name);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log(chalk.bold(`Convex teams linked to @${account.name}`));
|
||||
for (const [index, connection] of connections.entries()) {
|
||||
if (index > 0) {
|
||||
Log.newLine();
|
||||
}
|
||||
Log.log(formatConvexTeamConnection(connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../../commandUtils/EasCommand';
|
||||
import {
|
||||
formatConvexTeam,
|
||||
formatConvexTeamConnection,
|
||||
getConvexTeamDashboardUrl,
|
||||
logNoConvexTeams,
|
||||
} from '../../../../commandUtils/convex';
|
||||
import { EASNonInteractiveFlag } from '../../../../commandUtils/flags';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log, { link } from '../../../../log';
|
||||
import { ora } from '../../../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import { confirmAsync, selectAsync } from '../../../../prompts';
|
||||
|
||||
export default class IntegrationsConvexTeamDelete extends EasCommand {
|
||||
static override description =
|
||||
"remove a Convex team link from the current Expo app owner account's EAS servers";
|
||||
|
||||
static override args = {
|
||||
CONVEX_TEAM: Args.string({
|
||||
required: false,
|
||||
description: 'Slug of the Convex team to remove',
|
||||
}),
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
yes: Flags.boolean({
|
||||
char: 'y',
|
||||
description: 'Skip confirmation prompt',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { CONVEX_TEAM: team },
|
||||
flags: { 'non-interactive': nonInteractive, yes },
|
||||
} = await this.parse(IntegrationsConvexTeamDelete);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(IntegrationsConvexTeamDelete, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const connections = await ConvexQuery.getConvexTeamConnectionsByAccountIdAsync(
|
||||
graphqlClient,
|
||||
account.id
|
||||
);
|
||||
|
||||
if (connections.length === 0) {
|
||||
logNoConvexTeams(account.name);
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = await this.resolveConnectionAsync(connections, team, nonInteractive);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(formatConvexTeamConnection(connection));
|
||||
Log.newLine();
|
||||
|
||||
const dashboardUrl = getConvexTeamDashboardUrl(connection);
|
||||
if (!nonInteractive && !yes) {
|
||||
const confirmed = await confirmAsync({
|
||||
message: `Remove this Convex team link from EAS servers? This does not destroy resources on Convex. Convex dashboard: ${link(
|
||||
dashboardUrl,
|
||||
{ dim: false }
|
||||
)}`,
|
||||
});
|
||||
if (!confirmed) {
|
||||
Log.error('Canceled deletion of the Convex team link');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
`Removing the Convex team link from EAS servers. This does not destroy resources on Convex: ${dashboardUrl}`
|
||||
);
|
||||
}
|
||||
|
||||
const spinner = ora('Removing Convex team link').start();
|
||||
try {
|
||||
await ConvexMutation.deleteConvexTeamConnectionAsync(graphqlClient, connection.id);
|
||||
spinner.succeed(
|
||||
`Removed Convex team ${chalk.bold(formatConvexTeam(connection))} from EAS servers`
|
||||
);
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to remove Convex team link');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveConnectionAsync(
|
||||
connections: ConvexTeamConnectionData[],
|
||||
team: string | undefined,
|
||||
nonInteractive: boolean
|
||||
): Promise<ConvexTeamConnectionData> {
|
||||
if (team) {
|
||||
const connection = connections.find(
|
||||
item => item.convexTeamSlug === team || item.convexTeamName === team || item.id === team
|
||||
);
|
||||
if (!connection) {
|
||||
throw new Error(`Convex team ${team} is not linked to this account.`);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
if (connections.length === 1) {
|
||||
return connections[0];
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Convex team slug must be provided in non-interactive mode when multiple Convex team links exist.'
|
||||
);
|
||||
}
|
||||
|
||||
return await selectAsync(
|
||||
'Select a Convex team link to remove',
|
||||
connections.map(connection => ({
|
||||
title: formatConvexTeam(connection),
|
||||
value: connection,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Args } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../../commandUtils/EasCommand';
|
||||
import {
|
||||
confirmRecentConvexInviteAsync,
|
||||
formatConvexTeam,
|
||||
getConvexTeamDashboardUrl,
|
||||
logNoConvexTeams,
|
||||
} from '../../../../commandUtils/convex';
|
||||
import { EASNonInteractiveFlag } from '../../../../commandUtils/flags';
|
||||
import { ConvexMutation } from '../../../../graphql/mutations/ConvexMutation';
|
||||
import { ConvexQuery } from '../../../../graphql/queries/ConvexQuery';
|
||||
import { ConvexTeamConnectionData } from '../../../../graphql/types/ConvexTeamConnection';
|
||||
import Log, { link } from '../../../../log';
|
||||
import { ora } from '../../../../ora';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../../../../project/projectUtils';
|
||||
import { selectAsync } from '../../../../prompts';
|
||||
|
||||
export default class IntegrationsConvexTeamInvite extends EasCommand {
|
||||
static override description = 'send a Convex team invitation to your verified email address';
|
||||
|
||||
static override args = {
|
||||
CONVEX_TEAM: Args.string({
|
||||
required: false,
|
||||
description: 'Slug of the Convex team to invite yourself to',
|
||||
}),
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { CONVEX_TEAM: team },
|
||||
flags: { 'non-interactive': nonInteractive },
|
||||
} = await this.parse(IntegrationsConvexTeamInvite);
|
||||
|
||||
const {
|
||||
privateProjectConfig: { projectId },
|
||||
loggedIn: { graphqlClient, actor },
|
||||
} = await this.getContextAsync(IntegrationsConvexTeamInvite, {
|
||||
nonInteractive,
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
const email = this.getActorEmail(actor);
|
||||
if (!email) {
|
||||
Log.warn(
|
||||
`Could not determine your verified email address, so no Convex team invitation was sent. Run ${chalk.cyan(
|
||||
'eas integrations:convex:team:invite'
|
||||
)} after signing in with a user account.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const account = await getOwnerAccountForProjectIdAsync(graphqlClient, projectId);
|
||||
const connections = await ConvexQuery.getConvexTeamConnectionsByAccountIdAsync(
|
||||
graphqlClient,
|
||||
account.id
|
||||
);
|
||||
|
||||
if (connections.length === 0) {
|
||||
logNoConvexTeams(account.name);
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = await this.resolveConnectionAsync(connections, team, nonInteractive);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
this.logTeam(connection);
|
||||
this.logPreviousInvite(connection);
|
||||
Log.newLine();
|
||||
|
||||
if (!(await confirmRecentConvexInviteAsync(connection, { nonInteractive }))) {
|
||||
Log.warn('Skipped sending Convex team invitation.');
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = ora(`Sending Convex team invitation to ${email}`).start();
|
||||
try {
|
||||
await ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync(graphqlClient, {
|
||||
convexTeamConnectionId: connection.id,
|
||||
});
|
||||
spinner.succeed(
|
||||
`Sent Convex team invitation to ${chalk.bold(email)} for ${chalk.bold(formatConvexTeam(connection))}`
|
||||
);
|
||||
Log.log(`Convex dashboard: ${link(getConvexTeamDashboardUrl(connection), { dim: false })}`);
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to send Convex team invitation');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getActorEmail(actor: { __typename: string; [key: string]: any }): string | null {
|
||||
return 'email' in actor && typeof actor.email === 'string' ? actor.email : null;
|
||||
}
|
||||
|
||||
private logTeam(connection: ConvexTeamConnectionData): void {
|
||||
Log.log(chalk.bold(`Convex team ${formatConvexTeam(connection)}`));
|
||||
Log.log(
|
||||
`${chalk.bold('Dashboard')}: ${link(getConvexTeamDashboardUrl(connection), { dim: false })}`
|
||||
);
|
||||
}
|
||||
|
||||
private logPreviousInvite(connection: ConvexTeamConnectionData): void {
|
||||
if (!connection.invitedEmail && !connection.invitedAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.newLine();
|
||||
Log.log(chalk.bold('Previous invite'));
|
||||
if (connection.invitedEmail) {
|
||||
Log.log(`${chalk.bold('Email')}: ${connection.invitedEmail}`);
|
||||
}
|
||||
if (connection.invitedAt) {
|
||||
Log.log(`${chalk.bold('Sent at')}: ${connection.invitedAt}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveConnectionAsync(
|
||||
connections: ConvexTeamConnectionData[],
|
||||
team: string | undefined,
|
||||
nonInteractive: boolean
|
||||
): Promise<ConvexTeamConnectionData> {
|
||||
if (team) {
|
||||
const connection = connections.find(
|
||||
item => item.convexTeamSlug === team || item.convexTeamName === team || item.id === team
|
||||
);
|
||||
if (!connection) {
|
||||
throw new Error(`Convex team ${team} is not linked to this account.`);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
if (connections.length === 1) {
|
||||
return connections[0];
|
||||
}
|
||||
|
||||
if (nonInteractive) {
|
||||
throw new Error(
|
||||
'Convex team slug must be provided in non-interactive mode when multiple Convex team links exist.'
|
||||
);
|
||||
}
|
||||
|
||||
return await selectAsync(
|
||||
'Select a Convex team link to invite yourself to',
|
||||
connections.map(connection => ({
|
||||
title: formatConvexTeam(connection),
|
||||
value: connection,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ async function createFingerprintWithoutLoggingAsync(
|
||||
if (options.debug) {
|
||||
fingerprintOptions.debug = true;
|
||||
}
|
||||
fingerprintOptions.silent = true;
|
||||
|
||||
return await withTemporaryEnvAsync(options.env ?? {}, () =>
|
||||
Fingerprint.createFingerprintAsync(projectDir, fingerprintOptions)
|
||||
|
||||
+94
-1
@@ -3616,6 +3616,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
releaseChannel?: Maybe<Scalars['String']['output']>;
|
||||
requiredPackageManager?: Maybe<Scalars['String']['output']>;
|
||||
resolvedEnvironment?: Maybe<Scalars['EnvironmentVariableEnvironment']['output']>;
|
||||
resolvedImage?: Maybe<Scalars['String']['output']>;
|
||||
/**
|
||||
* The builder resource class requested by the developer
|
||||
* @deprecated Use resourceClassDisplayName instead
|
||||
@@ -3629,7 +3630,7 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
/** @deprecated Use 'runtime' field instead. */
|
||||
runtimeVersion?: Maybe<Scalars['String']['output']>;
|
||||
sdkVersion?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Check logs instead. */
|
||||
/** @deprecated Use 'resolvedImage' for the concrete image the build runs on. */
|
||||
selectedImage?: Maybe<Scalars['String']['output']>;
|
||||
status: BuildStatus;
|
||||
submissions: Array<Submission>;
|
||||
@@ -4231,12 +4232,32 @@ export type ConvexProject = {
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type ConvexProjectMutation = {
|
||||
__typename?: 'ConvexProjectMutation';
|
||||
deleteConvexProject: Scalars['ID']['output'];
|
||||
setupConvexProject: SetupConvexProjectResult;
|
||||
};
|
||||
|
||||
|
||||
export type ConvexProjectMutationDeleteConvexProjectArgs = {
|
||||
convexProjectId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ConvexProjectMutationSetupConvexProjectArgs = {
|
||||
input: SetupConvexProjectInput;
|
||||
};
|
||||
|
||||
export type ConvexTeamConnection = {
|
||||
__typename?: 'ConvexTeamConnection';
|
||||
account: Account;
|
||||
convexTeamIdentifier: Scalars['String']['output'];
|
||||
convexTeamName: Scalars['String']['output'];
|
||||
convexTeamSlug: Scalars['String']['output'];
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
invitedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
invitedEmail?: Maybe<Scalars['String']['output']>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
@@ -7836,6 +7857,7 @@ export type RootMutation = {
|
||||
build: BuildMutation;
|
||||
/** Mutations that create, update, and delete Build Annotations */
|
||||
buildAnnotation: BuildAnnotationMutation;
|
||||
convexProject: ConvexProjectMutation;
|
||||
convexTeamConnection: ConvexTeamConnectionMutation;
|
||||
customDomain: CustomDomainMutation;
|
||||
deployments: DeploymentsMutation;
|
||||
@@ -8434,6 +8456,21 @@ export type SentryProjectMutationDeleteSentryProjectArgs = {
|
||||
sentryProjectId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type SetupConvexProjectInput = {
|
||||
appId: Scalars['ID']['input'];
|
||||
convexTeamConnectionId: Scalars['ID']['input'];
|
||||
deploymentRegion?: InputMaybe<Scalars['String']['input']>;
|
||||
projectName: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type SetupConvexProjectResult = {
|
||||
__typename?: 'SetupConvexProjectResult';
|
||||
convexDeploymentName: Scalars['String']['output'];
|
||||
convexDeploymentUrl: Scalars['String']['output'];
|
||||
convexProject: ConvexProject;
|
||||
deployKey: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type SizeBreakdownCategory = {
|
||||
__typename?: 'SizeBreakdownCategory';
|
||||
assetCount: Scalars['Int']['output'];
|
||||
@@ -8750,6 +8787,7 @@ export type SubscriptionDetails = {
|
||||
endedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
futureSubscription?: Maybe<FutureSubscription>;
|
||||
id: Scalars['ID']['output'];
|
||||
includedAgentCreditsInCents: Scalars['Int']['output'];
|
||||
isDowngrading?: Maybe<Scalars['Boolean']['output']>;
|
||||
meteredBillingStatus: MeteredBillingStatus;
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
@@ -11904,6 +11942,41 @@ export type RetryIosBuildMutationVariables = Exact<{
|
||||
|
||||
export type RetryIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', retryIosBuild: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, logFiles: Array<string>, 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: 'PartnerActor', id: string, displayName: string } | { __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 } } };
|
||||
|
||||
export type CreateConvexTeamConnectionMutationVariables = Exact<{
|
||||
convexTeamConnectionData: CreateConvexTeamConnectionInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateConvexTeamConnectionMutation = { __typename?: 'RootMutation', convexTeamConnection: { __typename?: 'ConvexTeamConnectionMutation', createConvexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } } };
|
||||
|
||||
export type DeleteConvexTeamConnectionMutationVariables = Exact<{
|
||||
convexTeamConnectionId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteConvexTeamConnectionMutation = { __typename?: 'RootMutation', convexTeamConnection: { __typename?: 'ConvexTeamConnectionMutation', deleteConvexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } } };
|
||||
|
||||
export type SetupConvexProjectMutationVariables = Exact<{
|
||||
input: SetupConvexProjectInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type SetupConvexProjectMutation = { __typename?: 'RootMutation', convexProject: { __typename?: 'ConvexProjectMutation', setupConvexProject: { __typename?: 'SetupConvexProjectResult', convexDeploymentName: string, convexDeploymentUrl: string, deployKey: string, convexProject: { __typename?: 'ConvexProject', id: string, convexProjectIdentifier: string, convexProjectName: string, convexProjectSlug: string, createdAt: any, updatedAt: any, convexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } } } } };
|
||||
|
||||
export type DeleteConvexProjectMutationVariables = Exact<{
|
||||
convexProjectId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteConvexProjectMutation = { __typename?: 'RootMutation', convexProject: { __typename?: 'ConvexProjectMutation', deleteConvexProject: string } };
|
||||
|
||||
export type SendConvexTeamInviteToVerifiedEmailMutationVariables = Exact<{
|
||||
input: SendConvexTeamInviteToVerifiedEmailInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type SendConvexTeamInviteToVerifiedEmailMutation = { __typename?: 'RootMutation', convexTeamConnection: { __typename?: 'ConvexTeamConnectionMutation', sendConvexTeamInviteToVerifiedEmail: boolean } };
|
||||
|
||||
export type CreateDeviceRunSessionMutationVariables = Exact<{
|
||||
deviceRunSessionInput: CreateDeviceRunSessionInput;
|
||||
}>;
|
||||
@@ -12385,6 +12458,20 @@ export type ViewUpdateChannelsPaginatedOnAppQueryVariables = Exact<{
|
||||
|
||||
export type ViewUpdateChannelsPaginatedOnAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, channelsPaginated: { __typename?: 'AppChannelsConnection', edges: Array<{ __typename?: 'AppChannelEdge', cursor: string, node: { __typename?: 'UpdateChannel', id: string, name: string, branchMapping: string } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
|
||||
|
||||
export type ConvexTeamConnectionsByAccountIdQueryVariables = Exact<{
|
||||
accountId: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ConvexTeamConnectionsByAccountIdQuery = { __typename?: 'RootQuery', account: { __typename?: 'AccountQuery', byId: { __typename?: 'Account', id: string, convexTeamConnections: Array<{ __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null }> } } };
|
||||
|
||||
export type ConvexProjectByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ConvexProjectByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, convexProject?: { __typename?: 'ConvexProject', id: string, convexProjectIdentifier: string, convexProjectName: string, convexProjectSlug: string, createdAt: any, updatedAt: any, convexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } } | null } } };
|
||||
|
||||
export type DeviceRunSessionByIdQueryVariables = Exact<{
|
||||
deviceRunSessionId: Scalars['ID']['input'];
|
||||
}>;
|
||||
@@ -12672,6 +12759,12 @@ export type BuildWithSubmissionsFragment = { __typename?: 'Build', id: string, s
|
||||
|
||||
export type BuildWithFingerprintFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, logFiles: Array<string>, channel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, gitCommitMessage?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt: any, updatedAt: any, message?: string | null, completedAt?: any | null, expirationDate?: any | null, isForIosSimulator: boolean, fingerprint?: { __typename?: 'Fingerprint', id: string, hash: string, debugInfoUrl?: string | null, builds: { __typename?: 'AppBuildsConnection', edges: Array<{ __typename?: 'AppBuildEdge', node: { __typename?: 'Build', platform: AppPlatform, id: string } }> }, updates: { __typename?: 'AppUpdatesConnection', edges: Array<{ __typename?: 'AppUpdateEdge', node: { __typename?: 'Update', id: string, platform: string } }> } } | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null, applicationArchiveUrl?: string | null, buildArtifactsUrl?: string | null } | null, initiatingActor?: { __typename: 'PartnerActor', id: string, displayName: string } | { __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 };
|
||||
|
||||
export type ConvexTeamConnectionFragment = { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null };
|
||||
|
||||
export type ConvexProjectFragment = { __typename?: 'ConvexProject', id: string, convexProjectIdentifier: string, convexProjectName: string, convexProjectSlug: string, createdAt: any, updatedAt: any, convexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } };
|
||||
|
||||
export type SetupConvexProjectResultFragment = { __typename?: 'SetupConvexProjectResult', convexDeploymentName: string, convexDeploymentUrl: string, deployKey: string, convexProject: { __typename?: 'ConvexProject', id: string, convexProjectIdentifier: string, convexProjectName: string, convexProjectSlug: string, createdAt: any, updatedAt: any, convexTeamConnection: { __typename?: 'ConvexTeamConnection', id: string, convexTeamIdentifier: string, convexTeamName: string, convexTeamSlug: string, createdAt: any, updatedAt: any, invitedAt?: any | null, invitedEmail?: string | null } } };
|
||||
|
||||
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, type: EnvironmentSecretType, createdAt: any };
|
||||
|
||||
export type EnvironmentVariableFragment = { __typename?: 'EnvironmentVariable', id: string, name: string, value?: string | null, environments?: Array<any> | null, createdAt: any, updatedAt: any, scope: EnvironmentVariableScope, visibility?: EnvironmentVariableVisibility | null, type: EnvironmentSecretType };
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
CreateConvexTeamConnectionInput,
|
||||
SendConvexTeamInviteToVerifiedEmailInput,
|
||||
SetupConvexProjectInput,
|
||||
} from '../generated';
|
||||
import {
|
||||
ConvexProjectFragmentNode,
|
||||
ConvexTeamConnectionData,
|
||||
ConvexTeamConnectionFragmentNode,
|
||||
SetupConvexProjectResultData,
|
||||
SetupConvexProjectResultFragmentNode,
|
||||
} from '../types/ConvexTeamConnection';
|
||||
|
||||
type CreateConvexTeamConnectionMutation = {
|
||||
convexTeamConnection: {
|
||||
createConvexTeamConnection: ConvexTeamConnectionData;
|
||||
};
|
||||
};
|
||||
|
||||
type CreateConvexTeamConnectionMutationVariables = {
|
||||
convexTeamConnectionData: CreateConvexTeamConnectionInput;
|
||||
};
|
||||
|
||||
type DeleteConvexTeamConnectionMutation = {
|
||||
convexTeamConnection: {
|
||||
deleteConvexTeamConnection: ConvexTeamConnectionData;
|
||||
};
|
||||
};
|
||||
|
||||
type DeleteConvexTeamConnectionMutationVariables = {
|
||||
convexTeamConnectionId: string;
|
||||
};
|
||||
|
||||
type SetupConvexProjectMutation = {
|
||||
convexProject: {
|
||||
setupConvexProject: SetupConvexProjectResultData;
|
||||
};
|
||||
};
|
||||
|
||||
type SetupConvexProjectMutationVariables = {
|
||||
input: SetupConvexProjectInput;
|
||||
};
|
||||
|
||||
type DeleteConvexProjectMutation = {
|
||||
convexProject: {
|
||||
deleteConvexProject: string;
|
||||
};
|
||||
};
|
||||
|
||||
type DeleteConvexProjectMutationVariables = {
|
||||
convexProjectId: string;
|
||||
};
|
||||
|
||||
type SendConvexTeamInviteToVerifiedEmailMutation = {
|
||||
convexTeamConnection: {
|
||||
sendConvexTeamInviteToVerifiedEmail: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type SendConvexTeamInviteToVerifiedEmailMutationVariables = {
|
||||
input: SendConvexTeamInviteToVerifiedEmailInput;
|
||||
};
|
||||
|
||||
export const ConvexMutation = {
|
||||
async createConvexTeamConnectionAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
input: CreateConvexTeamConnectionInput
|
||||
): Promise<ConvexTeamConnectionData> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateConvexTeamConnectionMutation, CreateConvexTeamConnectionMutationVariables>(
|
||||
gql`
|
||||
mutation CreateConvexTeamConnection(
|
||||
$convexTeamConnectionData: CreateConvexTeamConnectionInput!
|
||||
) {
|
||||
convexTeamConnection {
|
||||
createConvexTeamConnection(
|
||||
convexTeamConnectionData: $convexTeamConnectionData
|
||||
) {
|
||||
id
|
||||
...ConvexTeamConnectionFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(ConvexTeamConnectionFragmentNode)}
|
||||
`,
|
||||
{ convexTeamConnectionData: input }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.convexTeamConnection.createConvexTeamConnection;
|
||||
},
|
||||
|
||||
async deleteConvexTeamConnectionAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
convexTeamConnectionId: string
|
||||
): Promise<ConvexTeamConnectionData> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteConvexTeamConnectionMutation, DeleteConvexTeamConnectionMutationVariables>(
|
||||
gql`
|
||||
mutation DeleteConvexTeamConnection($convexTeamConnectionId: ID!) {
|
||||
convexTeamConnection {
|
||||
deleteConvexTeamConnection(
|
||||
convexTeamConnectionId: $convexTeamConnectionId
|
||||
) {
|
||||
id
|
||||
...ConvexTeamConnectionFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(ConvexTeamConnectionFragmentNode)}
|
||||
`,
|
||||
{ convexTeamConnectionId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.convexTeamConnection.deleteConvexTeamConnection;
|
||||
},
|
||||
|
||||
async setupConvexProjectAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
input: SetupConvexProjectInput
|
||||
): Promise<SetupConvexProjectResultData> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<SetupConvexProjectMutation, SetupConvexProjectMutationVariables>(
|
||||
gql`
|
||||
mutation SetupConvexProject($input: SetupConvexProjectInput!) {
|
||||
convexProject {
|
||||
setupConvexProject(input: $input) {
|
||||
...SetupConvexProjectResultFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(ConvexTeamConnectionFragmentNode)}
|
||||
${print(ConvexProjectFragmentNode)}
|
||||
${print(SetupConvexProjectResultFragmentNode)}
|
||||
`,
|
||||
{ input }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.convexProject.setupConvexProject;
|
||||
},
|
||||
|
||||
async deleteConvexProjectAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
convexProjectId: string
|
||||
): Promise<string> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteConvexProjectMutation, DeleteConvexProjectMutationVariables>(
|
||||
gql`
|
||||
mutation DeleteConvexProject($convexProjectId: ID!) {
|
||||
convexProject {
|
||||
deleteConvexProject(convexProjectId: $convexProjectId)
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ convexProjectId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.convexProject.deleteConvexProject;
|
||||
},
|
||||
|
||||
async sendConvexTeamInviteToVerifiedEmailAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
input: SendConvexTeamInviteToVerifiedEmailInput
|
||||
): Promise<boolean> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<
|
||||
SendConvexTeamInviteToVerifiedEmailMutation,
|
||||
SendConvexTeamInviteToVerifiedEmailMutationVariables
|
||||
>(
|
||||
gql`
|
||||
mutation SendConvexTeamInviteToVerifiedEmail($input: SendConvexTeamInviteToVerifiedEmailInput!) {
|
||||
convexTeamConnection {
|
||||
sendConvexTeamInviteToVerifiedEmail(input: $input)
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ input }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.convexTeamConnection.sendConvexTeamInviteToVerifiedEmail;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
ConvexProjectData,
|
||||
ConvexProjectFragmentNode,
|
||||
ConvexTeamConnectionData,
|
||||
ConvexTeamConnectionFragmentNode,
|
||||
} from '../types/ConvexTeamConnection';
|
||||
|
||||
type ConvexTeamConnectionsByAccountIdQuery = {
|
||||
account: {
|
||||
byId: {
|
||||
id: string;
|
||||
convexTeamConnections: ConvexTeamConnectionData[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type ConvexTeamConnectionsByAccountIdQueryVariables = {
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
type ConvexProjectByAppIdQuery = {
|
||||
app: {
|
||||
byId: {
|
||||
id: string;
|
||||
convexProject?: ConvexProjectData | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type ConvexProjectByAppIdQueryVariables = {
|
||||
appId: string;
|
||||
};
|
||||
|
||||
export const ConvexQuery = {
|
||||
async getConvexTeamConnectionsByAccountIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountId: string
|
||||
): Promise<ConvexTeamConnectionData[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<
|
||||
ConvexTeamConnectionsByAccountIdQuery,
|
||||
ConvexTeamConnectionsByAccountIdQueryVariables
|
||||
>(
|
||||
gql`
|
||||
query ConvexTeamConnectionsByAccountId($accountId: String!) {
|
||||
account {
|
||||
byId(accountId: $accountId) {
|
||||
id
|
||||
convexTeamConnections {
|
||||
id
|
||||
...ConvexTeamConnectionFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(ConvexTeamConnectionFragmentNode)}
|
||||
`,
|
||||
{ accountId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data.account.byId.convexTeamConnections;
|
||||
},
|
||||
|
||||
async getConvexProjectByAppIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appId: string
|
||||
): Promise<ConvexProjectData | null> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ConvexProjectByAppIdQuery, ConvexProjectByAppIdQueryVariables>(
|
||||
gql`
|
||||
query ConvexProjectByAppId($appId: String!) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
convexProject {
|
||||
id
|
||||
...ConvexProjectFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(ConvexTeamConnectionFragmentNode)}
|
||||
${print(ConvexProjectFragmentNode)}
|
||||
`,
|
||||
{ appId },
|
||||
{ additionalTypenames: ['App', 'ConvexProject'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data.app.byId.convexProject ?? null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ConvexProject, ConvexTeamConnection, SetupConvexProjectResult } from '../generated';
|
||||
|
||||
export type ConvexTeamConnectionData = Pick<
|
||||
ConvexTeamConnection,
|
||||
| 'id'
|
||||
| 'convexTeamIdentifier'
|
||||
| 'convexTeamName'
|
||||
| 'convexTeamSlug'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'invitedAt'
|
||||
| 'invitedEmail'
|
||||
>;
|
||||
|
||||
export type ConvexProjectData = Pick<
|
||||
ConvexProject,
|
||||
| 'id'
|
||||
| 'convexProjectIdentifier'
|
||||
| 'convexProjectName'
|
||||
| 'convexProjectSlug'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
> & {
|
||||
convexTeamConnection: ConvexTeamConnectionData;
|
||||
};
|
||||
|
||||
export type SetupConvexProjectResultData = Pick<
|
||||
SetupConvexProjectResult,
|
||||
'convexDeploymentName' | 'convexDeploymentUrl' | 'deployKey'
|
||||
> & {
|
||||
convexProject: ConvexProjectData;
|
||||
};
|
||||
|
||||
export const ConvexTeamConnectionFragmentNode = gql`
|
||||
fragment ConvexTeamConnectionFragment on ConvexTeamConnection {
|
||||
id
|
||||
convexTeamIdentifier
|
||||
convexTeamName
|
||||
convexTeamSlug
|
||||
createdAt
|
||||
updatedAt
|
||||
invitedAt
|
||||
invitedEmail
|
||||
}
|
||||
`;
|
||||
|
||||
export const ConvexProjectFragmentNode = gql`
|
||||
fragment ConvexProjectFragment on ConvexProject {
|
||||
id
|
||||
convexProjectIdentifier
|
||||
convexProjectName
|
||||
convexProjectSlug
|
||||
createdAt
|
||||
updatedAt
|
||||
convexTeamConnection {
|
||||
id
|
||||
...ConvexTeamConnectionFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SetupConvexProjectResultFragmentNode = gql`
|
||||
fragment SetupConvexProjectResultFragment on SetupConvexProjectResult {
|
||||
convexDeploymentName
|
||||
convexDeploymentUrl
|
||||
deployKey
|
||||
convexProject {
|
||||
id
|
||||
...ConvexProjectFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli-local-build-plugin",
|
||||
"version": "18.9.0",
|
||||
"version": "18.10.0",
|
||||
"description": "Tool for running EAS compatible builds on a local machine.",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
@@ -26,7 +26,7 @@
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/build-tools": "18.9.0",
|
||||
"@expo/build-tools": "18.10.0",
|
||||
"@expo/eas-build-job": "18.8.0",
|
||||
"@expo/logger": "18.5.0",
|
||||
"@expo/spawn-async": "^1.7.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/worker",
|
||||
"version": "18.9.0",
|
||||
"version": "18.10.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "yarn watch",
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@expo/build-tools": "18.9.0",
|
||||
"@expo/build-tools": "18.10.0",
|
||||
"@expo/config-plugins": "55.0.7",
|
||||
"@expo/config-types": "55.0.5",
|
||||
"@expo/downloader": "18.5.0",
|
||||
|
||||
@@ -65,6 +65,11 @@ jest.mock('@expo/build-tools', () => {
|
||||
})),
|
||||
};
|
||||
});
|
||||
jest.mock('@expo/turtle-spawn', () =>
|
||||
jest.fn(async () => {
|
||||
throw new Error('Cannot find module expo/package.json');
|
||||
})
|
||||
);
|
||||
jest.mock('../service', () => {
|
||||
return function () {
|
||||
const BuildService = new (jest.requireActual('../service').default)();
|
||||
@@ -80,6 +85,9 @@ jest.mock('../config', () => {
|
||||
wwwApiV2BaseUrl: 'http://api.expo.test/v2/',
|
||||
};
|
||||
});
|
||||
jest.mock('../utils/turtleFetch', () => ({
|
||||
turtleFetch: jest.fn(async () => ({ ok: true })),
|
||||
}));
|
||||
jest.mock('../upload', () => {
|
||||
return {
|
||||
...jest.requireActual('../upload'),
|
||||
|
||||
@@ -2856,7 +2856,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/build-tools@npm:18.9.0, @expo/build-tools@workspace:packages/build-tools":
|
||||
"@expo/build-tools@npm:18.10.0, @expo/build-tools@workspace:packages/build-tools":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/build-tools@workspace:packages/build-tools"
|
||||
dependencies:
|
||||
@@ -3554,7 +3554,7 @@ __metadata:
|
||||
resolution: "@expo/worker@workspace:packages/worker"
|
||||
dependencies:
|
||||
"@babel/core": "npm:7.24.7"
|
||||
"@expo/build-tools": "npm:18.9.0"
|
||||
"@expo/build-tools": "npm:18.10.0"
|
||||
"@expo/config-plugins": "npm:55.0.7"
|
||||
"@expo/config-types": "npm:55.0.5"
|
||||
"@expo/downloader": "npm:18.5.0"
|
||||
@@ -10944,7 +10944,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "eas-cli-local-build-plugin@workspace:packages/local-build-plugin"
|
||||
dependencies:
|
||||
"@expo/build-tools": "npm:18.9.0"
|
||||
"@expo/build-tools": "npm:18.10.0"
|
||||
"@expo/eas-build-job": "npm:18.8.0"
|
||||
"@expo/logger": "npm:18.5.0"
|
||||
"@expo/spawn-async": "npm:^1.7.2"
|
||||
|
||||
Reference in new issue
Block a user