mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
84
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e998f5a942 | ||
|
|
c3f73fc466 | ||
|
|
08f938962b | ||
|
|
61109ea2c1 | ||
|
|
f889929f5f | ||
|
|
4bb314192e | ||
|
|
1a170283db | ||
|
|
ca803eed3f | ||
|
|
4b26472975 | ||
|
|
70b854abb1 | ||
|
|
2576a97704 | ||
|
|
15e50b1536 | ||
|
|
d6f3784cc0 | ||
|
|
a6f7d37d71 | ||
|
|
675c36a157 | ||
|
|
b2961a275b | ||
|
|
57928d1fd6 | ||
|
|
ff9d533248 | ||
|
|
e1ebcc0b8a | ||
|
|
628a7262cf | ||
|
|
05be162ec1 | ||
|
|
ff2fa425bd | ||
|
|
a621e395fc | ||
|
|
2260f74434 | ||
|
|
6dc9253d2d | ||
|
|
b6570bdfcf | ||
|
|
4d1675c96f | ||
|
|
00d9009967 | ||
|
|
b68840b76b | ||
|
|
6d8be295bf | ||
|
|
40c1cbe9f6 | ||
|
|
efae679089 | ||
|
|
6a2a3fe037 | ||
|
|
917987a41e | ||
|
|
bbe8fb9698 | ||
|
|
d656103357 | ||
|
|
4b7ad9f23a | ||
|
|
4db3979ab5 | ||
|
|
63e0bf2a24 | ||
|
|
a740aa174b | ||
|
|
f9ef83ba52 | ||
|
|
de8956c2ec | ||
|
|
41622c0cd3 | ||
|
|
d54abee7c3 | ||
|
|
cdd046b647 | ||
|
|
4577cf957d | ||
|
|
84b56f86eb | ||
|
|
b2580d0cc2 | ||
|
|
0f18ea01a9 | ||
|
|
fe9927862c | ||
|
|
bc2f6e60f4 | ||
|
|
6ac1e54f67 | ||
|
|
479e8d0b5d | ||
|
|
7ed62ec7f1 | ||
|
|
87000c36c1 | ||
|
|
a9d08f39ad | ||
|
|
55c156dd00 | ||
|
|
736d414cd6 | ||
|
|
85de580d84 | ||
|
|
1a65b2cc4e | ||
|
|
b3fca978d0 | ||
|
|
ba4c4e5954 | ||
|
|
383caa0354 | ||
|
|
2e43d4bba0 | ||
|
|
b995d1d713 | ||
|
|
d307f2343b | ||
|
|
9eeaee544a | ||
|
|
d9092e15f2 | ||
|
|
f90f085dcc | ||
|
|
61bb001059 | ||
|
|
9ef86f51fa | ||
|
|
44cd3b2669 | ||
|
|
0c706048be | ||
|
|
211a3fb6bf | ||
|
|
baeb88f5be | ||
|
|
ec3c27f744 | ||
|
|
054732c245 | ||
|
|
70c876776d | ||
|
|
0f45f938b1 | ||
|
|
c8313f36e7 | ||
|
|
a8cde5e81d | ||
|
|
d05142f9fc | ||
|
|
8e26c7c508 | ||
|
|
c29018df20 |
No files matched your search
@@ -69,16 +69,22 @@ jobs:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
- run: brew install python-setuptools
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn build
|
||||
- name: Build tarballs
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli
|
||||
run: |
|
||||
yarn pretarball-ci
|
||||
yarn oclif pack:tarballs --no-xz --targets darwin-x64
|
||||
yarn oclif pack:tarballs --no-xz --targets darwin-x64,darwin-arm64
|
||||
- id: x64
|
||||
run: echo "filename=$(ls eas-*-x64.tar.gz)" >> $GITHUB_OUTPUT
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
||||
- id: arm64
|
||||
run: echo "filename=$(ls eas-*-arm64.tar.gz)" >> $GITHUB_OUTPUT
|
||||
working-directory: ${{ github.workspace }}/packages/eas-cli/dist
|
||||
- name: Upload darwin-x64 tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -88,6 +94,15 @@ jobs:
|
||||
asset_path: packages/eas-cli/dist/${{ steps.x64.outputs.filename }}
|
||||
asset_name: ${{ steps.x64.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
- name: Upload darwin-arm64 tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
asset_path: packages/eas-cli/dist/${{ steps.arm64.outputs.filename }}
|
||||
asset_name: ${{ steps.arm64.outputs.filename }}
|
||||
asset_content_type: application/gzip
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
runs-on: ubuntu-latest
|
||||
@@ -211,3 +226,16 @@ jobs:
|
||||
SLACK_USERNAME: 'EAS CLI CI'
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
SLACK_FOOTER: ''
|
||||
open-pr-in-universe:
|
||||
name: Open a PR with `@expo/eas-json` bump in universe
|
||||
needs: publish-to-npm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Open a PR with `@expo/eas-json` version bump in universe
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
repo: expo/universe
|
||||
ref: main
|
||||
workflow: update-eas-json.yml
|
||||
token: ${{ secrets.EXPO_BOT_PAT }}
|
||||
+104
@@ -10,6 +10,110 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [9.0.7](https://github.com/expo/eas-cli/releases/tag/v9.0.7) - 2024-05-17
|
||||
|
||||
## [9.0.6](https://github.com/expo/eas-cli/releases/tag/v9.0.6) - 2024-05-15
|
||||
|
||||
## [9.0.5](https://github.com/expo/eas-cli/releases/tag/v9.0.5) - 2024-05-13
|
||||
|
||||
## [9.0.4](https://github.com/expo/eas-cli/releases/tag/v9.0.4) - 2024-05-13
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add loader and progress information to the `diagnostics` command. ([#2378](https://github.com/expo/eas-cli/pull/2378) by [@simek](https://github.com/simek))
|
||||
|
||||
## [9.0.3](https://github.com/expo/eas-cli/releases/tag/v9.0.3) - 2024-05-10
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2372](https://github.com/expo/eas-cli/pull/2372) by [@expo-bot](https://github.com/expo-bot))
|
||||
|
||||
## [9.0.2](https://github.com/expo/eas-cli/releases/tag/v9.0.2) - 2024-05-09
|
||||
|
||||
## [9.0.1](https://github.com/expo/eas-cli/releases/tag/v9.0.1) - 2024-05-09
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update image tags in `eas.schema.json`. ([#2363](https://github.com/expo/eas-cli/pull/2363) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [9.0.0](https://github.com/expo/eas-cli/releases/tag/v9.0.0) - 2024-05-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Allow modification of provisioning profile in CI, add --freeze-credentials flag. ([#2347](https://github.com/expo/eas-cli/pull/2347) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Pass env from process.env and build profile to expo-updates CLI calls where applicable. ([#2359](https://github.com/expo/eas-cli/pull/2359) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Remove more classic updates code. ([#2357](https://github.com/expo/eas-cli/pull/2357) by [@wschurman](https://github.com/wschurman))
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2360](https://github.com/expo/eas-cli/pull/2360) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Don't pass custom global `"expoCli"` version set in `eas.json` to EAS Build process and warn users when setting it. ([#2361](https://github.com/expo/eas-cli/pull/2361) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [8.0.0](https://github.com/expo/eas-cli/releases/tag/v8.0.0) - 2024-05-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Stop creating a channel on `eas update` and `eas update:roll-back-to-embedded` unless the `--channel` flag is specified. ([#2346](https://github.com/expo/eas-cli/pull/2346) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Improve login info message for other login options. ([#2352](https://github.com/expo/eas-cli/pull/2352) by [@wschurman](https://github.com/wschurman))
|
||||
- Show the -s, --sso option in the login command help. ([#2353](https://github.com/expo/eas-cli/pull/2353) by [@lzkb](https://github.com/lzkb))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Upgrade [`eas-build`](https://github.com/expo/eas-build) dependencies. ([#2351](https://github.com/expo/eas-cli/pull/2351) by [@expo-bot](https://github.com/expo-bot))
|
||||
- Don't prompt users to set push notifications by default if they don't have the `expo-notifications` installed. ([#2343](https://github.com/expo/eas-cli/pull/2343) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [7.8.5](https://github.com/expo/eas-cli/releases/tag/v7.8.5) - 2024-04-26
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Add explicit workflow arg to expo-update CLI calls. ([#2340](https://github.com/expo/eas-cli/pull/2340) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Improve error message if the server returns `UNAUTHORIZED_ERROR`. ([#2345](https://github.com/expo/eas-cli/pull/2345) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Fill in min expo-updates version for expo-updates CLI. ([#2344](https://github.com/expo/eas-cli/pull/2344) by [@wschurman](https://github.com/wschurman))
|
||||
|
||||
## [7.8.4](https://github.com/expo/eas-cli/releases/tag/v7.8.4) - 2024-04-24
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Update the list of available Android images. ([#2337](https://github.com/expo/eas-cli/pull/2337) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Make multi-select for revoking distribution certificates more readable. ([#2342](https://github.com/expo/eas-cli/pull/2342) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
- Improve error message displayed when EAS CLI version doesn't satisfy minimal version required specified in eas.json. ([#2341](https://github.com/expo/eas-cli/pull/2341) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
## [7.8.3](https://github.com/expo/eas-cli/releases/tag/v7.8.3) - 2024-04-23
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Don't prompt to set `android.package` and `ios.bundleIdentifier` values when running in non-interactive mode. ([#2336](https://github.com/expo/eas-cli/pull/2336) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Amend credential removal wording. ([#2334](https://github.com/expo/eas-cli/pull/2334) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [7.8.2](https://github.com/expo/eas-cli/releases/tag/v7.8.2) - 2024-04-15
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix display of errors when expo-updates CLI command fails. ([#2324](https://github.com/expo/eas-cli/pull/2324) by [@wschurman](https://github.com/wschurman))
|
||||
- Move credentials endpoints to paginated counterparts. ([#2327](https://github.com/expo/eas-cli/pull/2327) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add progress bar for fetching paginated datasets. ([#2326](https://github.com/expo/eas-cli/pull/2326) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [7.8.1](https://github.com/expo/eas-cli/releases/tag/v7.8.1) - 2024-04-11
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix command source files URLs in autogenerated `README`. ([#2318](https://github.com/expo/eas-cli/pull/2318) by [@szdziedzic](https://github.com/szdziedzic))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "7.8.1",
|
||||
"version": "9.0.7",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+70
-62
@@ -128,7 +128,10 @@ log in with your Expo account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas account:login
|
||||
$ eas account:login [-s]
|
||||
|
||||
FLAGS
|
||||
-s, --sso Login with SSO
|
||||
|
||||
DESCRIPTION
|
||||
log in with your Expo account
|
||||
@@ -137,7 +140,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -154,7 +157,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -171,7 +174,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -185,7 +188,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -237,7 +240,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -258,7 +261,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -278,7 +281,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -298,7 +301,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -321,7 +324,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -331,7 +334,7 @@ start a build
|
||||
USAGE
|
||||
$ eas build [-p android|ios|all] [-e <value>] [--local] [--output <value>] [--wait] [--clear-cache]
|
||||
[--auto-submit | --auto-submit-with-profile <value>] [-m <value>] [--build-logger-level
|
||||
trace|debug|info|warn|error|fatal] [--json --non-interactive]
|
||||
trace|debug|info|warn|error|fatal] [--freeze-credentials] [--json --non-interactive]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. Defaults to
|
||||
@@ -345,6 +348,8 @@ FLAGS
|
||||
--build-logger-level=(trace|debug|info|warn|error|fatal) The level of logs to output during the build process.
|
||||
Defaults to "info".
|
||||
--clear-cache Clear cache before the build
|
||||
--freeze-credentials Prevent the build from updating credentials in
|
||||
non-interactive mode
|
||||
--json Enable JSON output, non-JSON messages will be printed to
|
||||
stderr.
|
||||
--local Run build locally [experimental]
|
||||
@@ -356,7 +361,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -375,7 +380,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -392,7 +397,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -411,7 +416,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -446,7 +451,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -487,7 +492,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -513,7 +518,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -539,7 +544,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -589,7 +594,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -608,7 +613,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -627,7 +632,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -644,7 +649,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -665,7 +670,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -687,7 +692,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -707,7 +712,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -739,7 +744,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -762,7 +767,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -783,7 +788,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/v7.8.1/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -800,7 +805,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -818,7 +823,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -832,7 +837,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -852,7 +857,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -873,7 +878,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -894,7 +899,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -908,7 +913,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -922,7 +927,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -970,7 +975,10 @@ log in with your Expo account
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas login
|
||||
$ eas login [-s]
|
||||
|
||||
FLAGS
|
||||
-s, --sso Login with SSO
|
||||
|
||||
DESCRIPTION
|
||||
log in with your Expo account
|
||||
@@ -1010,7 +1018,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1027,7 +1035,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1044,7 +1052,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas open`
|
||||
|
||||
@@ -1058,7 +1066,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/v7.8.1/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1072,7 +1080,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1094,7 +1102,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -1117,7 +1125,7 @@ DESCRIPTION
|
||||
create an environment secret on the current project or owner account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -1135,7 +1143,7 @@ DESCRIPTION
|
||||
delete an environment secret by ID
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -1149,7 +1157,7 @@ DESCRIPTION
|
||||
list environment secrets available for your current app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas secret:push`
|
||||
|
||||
@@ -1169,7 +1177,7 @@ DESCRIPTION
|
||||
read environment secrets from env file and store on the server
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/secret/push.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1200,7 +1208,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v7.8.1/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v9.0.7/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1233,7 +1241,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1251,7 +1259,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1272,7 +1280,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1294,7 +1302,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1321,7 +1329,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -1348,7 +1356,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/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -1367,7 +1375,7 @@ DESCRIPTION
|
||||
roll back to an embedded update or an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -1387,7 +1395,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -1408,7 +1416,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -1428,7 +1436,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -1446,7 +1454,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -1468,7 +1476,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -1485,7 +1493,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v7.8.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/v9.0.7/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
|
||||
Generated
+2227
-1126
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "7.8.1",
|
||||
"version": "9.0.7",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -13,8 +13,8 @@
|
||||
"@expo/config": "8.5.4",
|
||||
"@expo/config-plugins": "7.8.4",
|
||||
"@expo/config-types": "50.0.0",
|
||||
"@expo/eas-build-job": "1.0.95",
|
||||
"@expo/eas-json": "7.8.0",
|
||||
"@expo/eas-build-job": "1.0.108",
|
||||
"@expo/eas-json": "9.0.4",
|
||||
"@expo/json-file": "8.2.37",
|
||||
"@expo/logger": "1.0.57",
|
||||
"@expo/multipart-body-parser": "1.1.0",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.0",
|
||||
"@expo/steps": "1.0.95",
|
||||
"@expo/steps": "1.0.108",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^1.26.2",
|
||||
"@oclif/plugin-autocomplete": "^2.3.10",
|
||||
|
||||
@@ -24,6 +24,7 @@ export function getMockAppFragment(): AppFragment {
|
||||
return {
|
||||
id: mockProjectId,
|
||||
slug: 'testapp',
|
||||
name: 'testapp',
|
||||
fullName: '@testuser/testpp',
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
|
||||
@@ -107,6 +107,7 @@ export async function prepareAndroidBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.buildProfile.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
|
||||
@@ -15,7 +15,6 @@ export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
triggeredBy: transformBuildTrigger(job.triggeredBy),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
|
||||
@@ -68,7 +68,6 @@ export async function prepareJobAsync(
|
||||
bun: buildProfile.bun,
|
||||
yarn: buildProfile.yarn,
|
||||
ndk: buildProfile.ndk,
|
||||
expoCli: buildProfile.expoCli,
|
||||
env: buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
@@ -79,7 +78,6 @@ export async function prepareJobAsync(
|
||||
secrets: {
|
||||
...buildCredentials,
|
||||
},
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
@@ -95,13 +93,18 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildProfile.config || ctx.repack ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { AndroidConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { AndroidVersionAutoIncrement } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
@@ -18,11 +18,13 @@ export async function syncProjectConfigurationAsync({
|
||||
exp,
|
||||
localAutoIncrement,
|
||||
vcsClient,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
localAutoIncrement?: AndroidVersionAutoIncrement;
|
||||
vcsClient: Client;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||
@@ -30,7 +32,7 @@ export async function syncProjectConfigurationAsync({
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await cleanUpOldEasBuildGradleScriptAsync(projectDir);
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(projectDir, exp);
|
||||
await syncUpdatesConfigurationAsync({ projectDir, exp, workflow, env });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy });
|
||||
} else {
|
||||
|
||||
@@ -62,4 +62,5 @@ export interface BuildContext<T extends Platform> {
|
||||
requiredPackageManager: NodePackageManager['name'] | null;
|
||||
vcsClient: Client;
|
||||
loggerLevel?: LoggerLevel;
|
||||
repack: boolean;
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/An
|
||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { getAppFromContextAsync } from '../credentials/ios/actions/BuildCredentialsUtils';
|
||||
import { GetAscApiKey } from '../credentials/ios/actions/GetAscApiKey';
|
||||
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
|
||||
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
@@ -41,6 +43,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel,
|
||||
freezeCredentials,
|
||||
repack,
|
||||
}: {
|
||||
buildProfileName: string;
|
||||
buildProfile: BuildProfile<T>;
|
||||
@@ -60,6 +64,8 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn;
|
||||
customBuildConfigMetadata?: CustomBuildConfigMetadata;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}): Promise<BuildContext<T>> {
|
||||
const { exp, projectId } = await getDynamicPrivateProjectConfigAsync({ env: buildProfile.env });
|
||||
const projectName = exp.slug;
|
||||
@@ -86,6 +92,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
env: buildProfile.env,
|
||||
easJsonCliConfig,
|
||||
vcsClient,
|
||||
freezeCredentials,
|
||||
});
|
||||
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
@@ -140,6 +147,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
developmentClient,
|
||||
requiredPackageManager,
|
||||
loggerLevel: buildLoggerLevel,
|
||||
repack,
|
||||
};
|
||||
if (platform === Platform.ANDROID) {
|
||||
const common = commonContext as CommonContext<Platform.ANDROID>;
|
||||
@@ -149,10 +157,23 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
} as BuildContext<T>;
|
||||
} else {
|
||||
const common = commonContext as CommonContext<Platform.IOS>;
|
||||
return {
|
||||
const iosBuildCtx = {
|
||||
...common,
|
||||
ios: await createIosContextAsync(common),
|
||||
} as BuildContext<T>;
|
||||
const appLookupParams = {
|
||||
...(await getAppFromContextAsync(credentialsCtx)),
|
||||
bundleIdentifier: (iosBuildCtx as BuildContext<Platform.IOS>).ios.bundleIdentifier,
|
||||
};
|
||||
const ascForBuilds = await GetAscApiKey.getForBuildServiceAsync(
|
||||
appLookupParams,
|
||||
credentialsCtx
|
||||
);
|
||||
if (ascForBuilds) {
|
||||
credentialsCtx.configureAppStoreAuth(ascForBuilds);
|
||||
}
|
||||
|
||||
return iosBuildCtx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ export async function prepareIosBuildAsync(
|
||||
? false
|
||||
: ctx.buildProfile.autoIncrement,
|
||||
vcsClient: ctx.vcsClient,
|
||||
env: ctx.buildProfile.env,
|
||||
});
|
||||
},
|
||||
prepareJobAsync: async (
|
||||
|
||||
@@ -16,7 +16,6 @@ export function transformJob(job: Ios.Job): IosJobInput {
|
||||
triggeredBy: transformBuildTrigger(job.triggeredBy),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: nullthrows(job.projectRootDirectory),
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets ? transformIosSecrets(job.secrets) : undefined,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
|
||||
@@ -62,7 +62,6 @@ export async function prepareJobAsync(
|
||||
bundler: buildProfile.bundler,
|
||||
cocoapods: buildProfile.cocoapods,
|
||||
fastlane: buildProfile.fastlane,
|
||||
expoCli: buildProfile.expoCli,
|
||||
env: buildProfile.env,
|
||||
},
|
||||
cache: {
|
||||
@@ -73,7 +72,6 @@ export async function prepareJobAsync(
|
||||
secrets: {
|
||||
buildCredentials,
|
||||
},
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
developmentClient: buildProfile.developmentClient,
|
||||
simulator: buildProfile.simulator,
|
||||
@@ -90,13 +88,18 @@ export async function prepareJobAsync(
|
||||
experimental: {
|
||||
prebuildCommand: buildProfile.prebuildCommand,
|
||||
},
|
||||
mode: buildProfile.config ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
mode: buildProfile.config || ctx.repack ? BuildMode.CUSTOM : BuildMode.BUILD,
|
||||
triggeredBy: BuildTrigger.EAS_CLI,
|
||||
...(maybeCustomBuildConfigPath && {
|
||||
customBuildConfig: {
|
||||
path: maybeCustomBuildConfigPath,
|
||||
},
|
||||
}),
|
||||
...(ctx.repack && {
|
||||
customBuildConfig: {
|
||||
path: '__eas/repack.yml',
|
||||
},
|
||||
}),
|
||||
loggerLevel: ctx.loggerLevel,
|
||||
};
|
||||
return sanitizeBuildJob(job) as Ios.Job;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { IosVersionAutoIncrement } from '@expo/eas-json';
|
||||
|
||||
import { BumpStrategy, bumpVersionAsync, bumpVersionInAppJsonAsync } from './version';
|
||||
@@ -15,19 +15,21 @@ export async function syncProjectConfigurationAsync({
|
||||
targets,
|
||||
localAutoIncrement,
|
||||
vcsClient,
|
||||
env,
|
||||
}: {
|
||||
projectDir: string;
|
||||
exp: ExpoConfig;
|
||||
targets: Target[];
|
||||
localAutoIncrement?: IosVersionAutoIncrement;
|
||||
vcsClient: Client;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(localAutoIncrement ?? false);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
if (isExpoUpdatesInstalled(projectDir)) {
|
||||
await syncUpdatesConfigurationAsync(vcsClient, projectDir, exp);
|
||||
await syncUpdatesConfigurationAsync({ vcsClient, projectDir, exp, workflow, env });
|
||||
}
|
||||
await bumpVersionAsync({ projectDir, exp, bumpStrategy: versionBumpStrategy, targets });
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ import semver from 'semver';
|
||||
import { ora } from '../ora';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.97';
|
||||
const PLUGIN_PACKAGE_VERSION = '1.0.112';
|
||||
|
||||
export enum LocalBuildMode {
|
||||
/**
|
||||
|
||||
@@ -9,26 +9,16 @@ import { maybeResolveVersionsAsync as maybeResolveIosVersionsAsync } from './ios
|
||||
import { LocalBuildMode } from './local';
|
||||
import { BuildDistributionType } from './types';
|
||||
import Log from '../log';
|
||||
import {
|
||||
getUsername,
|
||||
isClassicUpdatesSupportedAsync,
|
||||
isExpoUpdatesInstalled,
|
||||
} from '../project/projectUtils';
|
||||
import { getUsername, isExpoUpdatesInstalled } from '../project/projectUtils';
|
||||
import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync';
|
||||
import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
} from '../update/android/UpdatesModule';
|
||||
import {
|
||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readIosReleaseChannelSafelyAsync,
|
||||
} from '../update/ios/UpdatesModule';
|
||||
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
||||
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<Metadata> {
|
||||
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
|
||||
const channelObject = await resolveChannelAsync(ctx);
|
||||
const distribution = ctx.buildProfile.distribution ?? BuildDistributionType.STORE;
|
||||
const metadata: Metadata = {
|
||||
trackingContext: ctx.analyticsEventProperties,
|
||||
@@ -37,9 +27,17 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource: ctx.buildProfile.credentialsSource,
|
||||
sdkVersion: ctx.exp.sdkVersion,
|
||||
runtimeVersion: (await resolveRuntimeVersionAsync(ctx)) ?? undefined,
|
||||
runtimeVersion:
|
||||
(await resolveRuntimeVersionAsync({
|
||||
exp: ctx.exp,
|
||||
platform: ctx.platform,
|
||||
workflow: ctx.workflow,
|
||||
projectDir: ctx.projectDir,
|
||||
env: ctx.buildProfile.env,
|
||||
cwd: ctx.projectDir,
|
||||
})) ?? undefined,
|
||||
reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir),
|
||||
...channelOrReleaseChannel,
|
||||
...channelObject,
|
||||
distribution,
|
||||
appName: ctx.exp.name,
|
||||
appIdentifier: resolveAppIdentifier(ctx),
|
||||
@@ -117,9 +115,9 @@ function resolveAppIdentifier<T extends Platform>(ctx: BuildContext<T>): string
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
|
||||
async function resolveChannelAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<{ channel: string } | { releaseChannel: string } | null> {
|
||||
): Promise<{ channel: string } | null> {
|
||||
if (!isExpoUpdatesInstalled(ctx.projectDir)) {
|
||||
return null;
|
||||
}
|
||||
@@ -131,31 +129,7 @@ async function resolveChannelOrReleaseChannelAsync<T extends Platform>(
|
||||
return { channel };
|
||||
}
|
||||
|
||||
if (!(await isClassicUpdatesSupportedAsync(ctx.projectDir))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx.buildProfile.releaseChannel) {
|
||||
return { releaseChannel: ctx.buildProfile.releaseChannel };
|
||||
}
|
||||
|
||||
const releaseChannel = await getNativeReleaseChannelAsync(ctx);
|
||||
return { releaseChannel };
|
||||
}
|
||||
|
||||
async function getNativeReleaseChannelAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<string> {
|
||||
switch (ctx.platform) {
|
||||
case Platform.ANDROID: {
|
||||
return (await readAndroidReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
case Platform.IOS: {
|
||||
return (await readIosReleaseChannelSafelyAsync(ctx.projectDir)) ?? 'default';
|
||||
}
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getNativeChannelAsync<T extends Platform>(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { Env, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import {
|
||||
AppVersionSource,
|
||||
BuildProfile,
|
||||
@@ -71,7 +71,6 @@ import {
|
||||
} from '../submit/submit';
|
||||
import { printSubmissionDetailsUrls } from '../submit/utils/urls';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../update/configure';
|
||||
import { validateBuildProfileConfigMatchesProjectConfigAsync } from '../update/utils';
|
||||
import { Actor } from '../user/User';
|
||||
import { downloadAndMaybeExtractAppAsync } from '../utils/download';
|
||||
import { truthy } from '../utils/expodash/filter';
|
||||
@@ -95,6 +94,8 @@ export interface BuildFlags {
|
||||
resourceClass?: ResourceClass;
|
||||
message?: string;
|
||||
buildLoggerLevel?: LoggerLevel;
|
||||
freezeCredentials: boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export async function runBuildAndSubmitAsync(
|
||||
@@ -105,7 +106,9 @@ export async function runBuildAndSubmitAsync(
|
||||
flags: BuildFlags,
|
||||
actor: Actor,
|
||||
getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn
|
||||
): Promise<void> {
|
||||
): Promise<{
|
||||
buildIds: string[];
|
||||
}> {
|
||||
await vcsClient.ensureRepoExistsAsync();
|
||||
await ensureRepoIsCleanAsync(vcsClient, flags.nonInteractive);
|
||||
|
||||
@@ -213,7 +216,9 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL) {
|
||||
@@ -269,14 +274,18 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
|
||||
if (flags.localBuildOptions.localBuildMode) {
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (!flags.wait) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(startedBuilds.map(buildInfo => buildInfo.build));
|
||||
}
|
||||
return;
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
const { accountName } = Object.values(buildCtxByPlatform)[0];
|
||||
@@ -317,6 +326,9 @@ export async function runBuildAndSubmitAsync(
|
||||
}
|
||||
exitWithNonZeroCodeIfSomeSubmissionsDidntFinish(completedSubmissions);
|
||||
}
|
||||
return {
|
||||
buildIds: startedBuilds.map(({ build }) => build.id),
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareAndStartBuildAsync({
|
||||
@@ -363,6 +375,8 @@ async function prepareAndStartBuildAsync({
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
customBuildConfigMetadata,
|
||||
buildLoggerLevel: flags.buildLoggerLevel,
|
||||
freezeCredentials: flags.freezeCredentials,
|
||||
repack: flags.repack,
|
||||
});
|
||||
|
||||
if (moreBuilds) {
|
||||
@@ -375,12 +389,6 @@ async function prepareAndStartBuildAsync({
|
||||
);
|
||||
}
|
||||
|
||||
await validateBuildProfileConfigMatchesProjectConfigAsync(
|
||||
buildCtx.exp,
|
||||
buildProfile,
|
||||
buildCtx.projectId,
|
||||
flags.nonInteractive
|
||||
);
|
||||
if (buildProfile.profile.channel) {
|
||||
await validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
exp: buildCtx.exp,
|
||||
@@ -390,6 +398,7 @@ async function prepareAndStartBuildAsync({
|
||||
sdkVersion: buildCtx.exp.sdkVersion,
|
||||
nonInteractive: flags.nonInteractive,
|
||||
buildProfile,
|
||||
env: buildProfile.profile.env,
|
||||
});
|
||||
if (isUsingEASUpdate(buildCtx.exp, buildCtx.projectId)) {
|
||||
const doesChannelExist = await doesChannelExistAsync(graphqlClient, {
|
||||
@@ -534,6 +543,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
buildProfile,
|
||||
nonInteractive,
|
||||
sdkVersion,
|
||||
env,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
projectId: string;
|
||||
@@ -542,6 +552,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
buildProfile: ProfileData<'build'>;
|
||||
nonInteractive: boolean;
|
||||
sdkVersion?: string;
|
||||
env: Env | undefined;
|
||||
}): Promise<void> {
|
||||
if (isExpoUpdatesInstalledOrAvailable(projectDir, sdkVersion)) {
|
||||
return;
|
||||
@@ -569,6 +580,7 @@ async function validateExpoUpdatesInstalledAsProjectDependencyAsync({
|
||||
projectDir,
|
||||
platform: RequestedPlatform.All,
|
||||
vcsClient,
|
||||
env,
|
||||
});
|
||||
Log.withTick('Installed expo-updates and configured EAS Update.');
|
||||
throw new Error('Command must be re-run to pick up new updates configuration.');
|
||||
|
||||
@@ -51,10 +51,6 @@ export function formatGraphQLBuild(build: BuildFragment): string {
|
||||
label: 'Enterprise Provisioning',
|
||||
value: build.iosEnterpriseProvisioning?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Release Channel',
|
||||
value: build.releaseChannel,
|
||||
},
|
||||
{
|
||||
label: 'Channel',
|
||||
value: build.channel,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Command } from '@oclif/core';
|
||||
import { CombinedError } from '@urql/core';
|
||||
import chalk from 'chalk';
|
||||
import { GraphQLError } from 'graphql/error';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
@@ -210,7 +211,13 @@ export default abstract class EasCommand extends Command {
|
||||
const defaultMsg = `${messageLine}${requestIdLine}`;
|
||||
|
||||
if (graphQLError.extensions?.errorCode === 'UNAUTHORIZED_ERROR') {
|
||||
return `You don't have the required permissions to perform this operation.\n\n${defaultMsg}`;
|
||||
return `${chalk.bold(
|
||||
`You don't have the required permissions to perform this operation.`
|
||||
)}\n\nThis can sometimes happen if you are logged in as incorrect user.\nRun ${chalk.bold(
|
||||
'eas whoami'
|
||||
)} to check the username you are logged in as.\nRun ${chalk.bold(
|
||||
'eas login'
|
||||
)} to change the account.\n\nOriginal error message: ${defaultMsg}`;
|
||||
}
|
||||
|
||||
return defaultMsg;
|
||||
|
||||
+7
@@ -55,6 +55,7 @@ describe(getProjectIdAsync, () => {
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
preferences: {},
|
||||
},
|
||||
authenticationInfo: { accessToken: 'fake', sessionSecret: null },
|
||||
});
|
||||
@@ -79,6 +80,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -99,6 +101,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -123,6 +126,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
@@ -171,6 +175,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@totallybrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'totallybrent' } as any,
|
||||
});
|
||||
@@ -195,6 +200,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
@@ -264,6 +270,7 @@ describe(getProjectIdAsync, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
fullName: '@notnotbrent/test',
|
||||
name: 'test',
|
||||
slug: 'test',
|
||||
ownerAccount: { name: 'notnotbrent' } as any,
|
||||
});
|
||||
|
||||
+5
-1
@@ -15,7 +15,11 @@ async function applyCliConfigAsync(projectDir: string): Promise<void> {
|
||||
const config = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
|
||||
if (config?.version && !semver.satisfies(easCliVersion, config.version)) {
|
||||
throw new Error(
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint in eas.json (${config.version})`
|
||||
`You are on eas-cli@${easCliVersion} which does not satisfy the CLI version constraint defined in eas.json (${
|
||||
config.version
|
||||
}).\n\nThis error probably means that you need update your eas-cli to a newer version.\nRun ${chalk.bold(
|
||||
'npm install -g eas-cli'
|
||||
)} to update the eas-cli to the latest version.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ExpoConfig } from '@expo/config-types';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createGraphqlClient } from './createGraphqlClient';
|
||||
import { ExpoGraphqlClient, createGraphqlClient } from './createGraphqlClient';
|
||||
import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync';
|
||||
import { AppQuery } from '../../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
@@ -87,6 +87,21 @@ export async function getProjectIdAsync(
|
||||
});
|
||||
const graphqlClient = createGraphqlClient(authenticationInfo);
|
||||
|
||||
const projectId = await validateOrSetProjectIdAsync({ exp, graphqlClient, actor, options });
|
||||
return projectId;
|
||||
}
|
||||
|
||||
export async function validateOrSetProjectIdAsync({
|
||||
exp,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
}: {
|
||||
exp: ExpoConfig;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
options: { env?: Env; nonInteractive: boolean };
|
||||
}): Promise<string> {
|
||||
const localProjectId = exp.extra?.eas?.projectId;
|
||||
if (localProjectId) {
|
||||
if (typeof localProjectId !== 'string') {
|
||||
|
||||
@@ -13,7 +13,6 @@ export default class AccountLogin extends EasCommand {
|
||||
description: 'Login with SSO',
|
||||
char: 's',
|
||||
default: false,
|
||||
hidden: true,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -74,14 +74,20 @@ export default class BuildConfigure extends EasCommand {
|
||||
if ([RequestedPlatform.Android, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncAndroidUpdatesConfigurationAsync(projectDir, exp);
|
||||
await syncAndroidUpdatesConfigurationAsync({ projectDir, exp, workflow, env: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
if ([RequestedPlatform.Ios, RequestedPlatform.All].includes(platform)) {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
await syncIosUpdatesConfigurationAsync(vcsClient, projectDir, exp);
|
||||
await syncIosUpdatesConfigurationAsync({
|
||||
vcsClient,
|
||||
projectDir,
|
||||
exp,
|
||||
workflow,
|
||||
env: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ interface RawBuildFlags {
|
||||
'resource-class'?: ResourceClass;
|
||||
message?: string;
|
||||
'build-logger-level'?: LoggerLevel;
|
||||
'freeze-credentials': boolean;
|
||||
repack: boolean;
|
||||
}
|
||||
|
||||
export default class Build extends EasCommand {
|
||||
@@ -104,6 +106,15 @@ export default class Build extends EasCommand {
|
||||
description: 'The level of logs to output during the build process. Defaults to "info".',
|
||||
options: Object.values(LoggerLevel),
|
||||
}),
|
||||
'freeze-credentials': Flags.boolean({
|
||||
default: false,
|
||||
description: 'Prevent the build from updating credentials in non-interactive mode',
|
||||
}),
|
||||
repack: Flags.boolean({
|
||||
default: false,
|
||||
hidden: true,
|
||||
description: 'Use the golden dev client build repack flow as it works for onboarding',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
@@ -217,6 +228,8 @@ export default class Build extends EasCommand {
|
||||
resourceClass: flags['resource-class'],
|
||||
message,
|
||||
buildLoggerLevel: flags['build-logger-level'],
|
||||
freezeCredentials: flags['freeze-credentials'],
|
||||
repack: flags.repack,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ export default class BuildInspect extends EasCommand {
|
||||
projectDir,
|
||||
{
|
||||
nonInteractive: false,
|
||||
freezeCredentials: false,
|
||||
wait: true,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
@@ -119,6 +120,7 @@ export default class BuildInspect extends EasCommand {
|
||||
workingdir: tmpWorkingdir,
|
||||
artifactsDir: path.join(tmpWorkingdir, 'artifacts'),
|
||||
},
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
|
||||
@@ -77,6 +77,7 @@ export default class BuildInternal extends EasCommand {
|
||||
requestedPlatform: flags.platform as RequestedPlatform,
|
||||
profile: flags.profile,
|
||||
nonInteractive: true,
|
||||
freezeCredentials: false,
|
||||
wait: false,
|
||||
clearCache: false,
|
||||
json: true,
|
||||
@@ -85,6 +86,7 @@ export default class BuildInternal extends EasCommand {
|
||||
localBuildMode: LocalBuildMode.INTERNAL,
|
||||
},
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? flags.profile,
|
||||
repack: false,
|
||||
},
|
||||
actor,
|
||||
getDynamicPrivateProjectConfigAsync
|
||||
|
||||
@@ -88,6 +88,7 @@ export default class BuildVersionGetView extends EasCommand {
|
||||
buildProfile: profile,
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: flags['non-interactive'],
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -77,6 +77,7 @@ export default class BuildVersionSetView extends EasCommand {
|
||||
buildProfile: profile,
|
||||
platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -102,6 +102,7 @@ export default class BuildVersionSyncView extends EasCommand {
|
||||
buildProfile: profileInfo.profile,
|
||||
platform: profileInfo.platform,
|
||||
vcsClient,
|
||||
nonInteractive: false,
|
||||
});
|
||||
const remoteVersions = await AppVersionQuery.latestVersionAsync(
|
||||
graphqlClient,
|
||||
|
||||
@@ -125,6 +125,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectConfig,
|
||||
...this.ContextOptions.Vcs,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
@@ -133,6 +134,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
const argsAndFlags = this.sanitizeArgsAndFlags({ ...flags, ...args });
|
||||
const {
|
||||
privateProjectConfig: { exp, projectId, projectDir },
|
||||
vcsClient,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(ChannelRollout, {
|
||||
nonInteractive: argsAndFlags.nonInteractive,
|
||||
@@ -146,6 +148,7 @@ export default class ChannelRollout extends EasCommand {
|
||||
nonInteractive: argsAndFlags.nonInteractive,
|
||||
graphqlClient,
|
||||
app,
|
||||
vcsClient,
|
||||
};
|
||||
if (argsAndFlags.nonInteractive) {
|
||||
await new NonInteractiveRollout(argsAndFlags).runAsync(ctx);
|
||||
|
||||
@@ -58,7 +58,8 @@ export default class InitializeBuildCredentials extends EasCommand {
|
||||
privateProjectConfig ?? null,
|
||||
getDynamicPrivateProjectConfigAsync,
|
||||
platform,
|
||||
buildProfile
|
||||
buildProfile,
|
||||
process.cwd()
|
||||
).runAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
import envinfo from 'envinfo';
|
||||
|
||||
import EasCommand from '../commandUtils/EasCommand';
|
||||
import Log from '../log';
|
||||
import { ora } from '../ora';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { Client } from '../vcs/vcs';
|
||||
@@ -20,6 +22,7 @@ export default class Diagnostics extends EasCommand {
|
||||
nonInteractive: true,
|
||||
});
|
||||
|
||||
const spinner = ora().start(`Gathering diagnostic information...`);
|
||||
const info = await envinfo.run(
|
||||
{
|
||||
System: ['OS', 'Shell'],
|
||||
@@ -40,9 +43,10 @@ export default class Diagnostics extends EasCommand {
|
||||
npmGlobalPackages: ['eas-cli', 'expo-cli'],
|
||||
},
|
||||
{
|
||||
title: `EAS CLI ${easCliVersion} environment info`,
|
||||
title: chalk.bold(`EAS CLI ${easCliVersion} environment info`),
|
||||
}
|
||||
);
|
||||
spinner.succeed('All needed information gathered!');
|
||||
|
||||
Log.log(info.trimEnd());
|
||||
await this.printWorkflowAsync(projectDir, vcsClient);
|
||||
@@ -56,7 +60,7 @@ export default class Diagnostics extends EasCommand {
|
||||
if (androidWorkflow === iosWorkflow) {
|
||||
Log.log(` Project workflow: ${androidWorkflow}`);
|
||||
} else {
|
||||
Log.log(` Project Workflow:`);
|
||||
Log.log(` Project workflow:`);
|
||||
Log.log(` Android: ${androidWorkflow}`);
|
||||
Log.log(` iOS: ${iosWorkflow}`);
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ describe(ProjectInit.name, () => {
|
||||
it('is no-op if already configured for id', async () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
name: 'testing-123',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
@@ -121,6 +122,7 @@ describe(ProjectInit.name, () => {
|
||||
it('prompts to overwrite when different', async () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
name: 'testing-123',
|
||||
slug: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
@@ -135,6 +137,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -152,6 +155,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -186,6 +190,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -198,6 +203,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -223,6 +229,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -247,6 +254,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -273,6 +281,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -288,6 +297,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -302,6 +312,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -329,6 +340,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -355,6 +367,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -384,6 +397,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -398,6 +412,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-124',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester2/testing-124',
|
||||
ownerAccount: jester2.accounts[0],
|
||||
});
|
||||
@@ -437,6 +452,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
@@ -464,6 +480,7 @@ describe(ProjectInit.name, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '0129',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { EasJson } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { getEASUpdateURL, getExpoWebsiteBaseUrl } from '../../api';
|
||||
import { runBuildAndSubmitAsync } from '../../build/runBuildAndSubmit';
|
||||
import { reviewAndCommitChangesAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { DynamicConfigContextFn } from '../../commandUtils/context/DynamicProjectConfigContextField';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { validateOrSetProjectIdAsync } from '../../commandUtils/context/contextUtils/getProjectIdAsync';
|
||||
import { CredentialsContextProjectInfo } from '../../credentials/context';
|
||||
import { SetUpBuildCredentialsCommandAction } from '../../credentials/manager/SetUpBuildCredentialsCommandAction';
|
||||
import {
|
||||
AppFragment,
|
||||
AppPlatform,
|
||||
OnboardingDeviceType,
|
||||
OnboardingEnvironment,
|
||||
} from '../../graphql/generated';
|
||||
import { UserPreferencesMutation } from '../../graphql/mutations/UserPreferencesMutation';
|
||||
import { AppQuery } from '../../graphql/queries/AppQuery';
|
||||
import Log, { learnMore, link } from '../../log';
|
||||
import { runGitCloneAsync, runGitPushAsync } from '../../onboarding/git';
|
||||
import { installDependenciesAsync } from '../../onboarding/installDependencies';
|
||||
import { runCommandAsync } from '../../onboarding/runCommand';
|
||||
import { RequestedPlatform } from '../../platform';
|
||||
import { ExpoConfigOptions, getPrivateExpoConfig } from '../../project/expoConfig';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { Actor } from '../../user/User';
|
||||
import { easCliVersion } from '../../utils/easCli';
|
||||
import GitClient from '../../vcs/clients/git';
|
||||
|
||||
export default class Onboarding extends EasCommand {
|
||||
static override hidden = true;
|
||||
|
||||
static override aliases = ['init:onboarding', 'onboarding'];
|
||||
|
||||
static override description = 'continue onboarding process started on the expo.dev website';
|
||||
|
||||
static override flags = {};
|
||||
|
||||
static override args = [{ name: 'TARGET_PROJECT_DIRECTORY' }];
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
...this.ContextOptions.Analytics,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { TARGET_PROJECT_DIRECTORY: targetProjectDirInput },
|
||||
} = await this.parse(Onboarding);
|
||||
|
||||
const {
|
||||
loggedIn: { actor, graphqlClient },
|
||||
analytics,
|
||||
} = await this.getContextAsync(Onboarding, {
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
if (actor.__typename === 'Robot') {
|
||||
throw new Error(
|
||||
'This command is not available for robot users. Make sure you are not using a robot token and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!actor.preferences.onboarding) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. Visit https://expo.new to start a new project.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!actor.preferences.onboarding.platform) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected platform). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
if (!actor.preferences.onboarding.environment) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected environment). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
if (!actor.preferences.onboarding.deviceType) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. It seems like you started an onboarding process, but we are missing some information needed to be filled in before running the eas init:onboarding command (selected device type). Continue the onboarding process on the Expo website.'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
new Date(actor.preferences.onboarding.lastUsed) < new Date(Date.now() - 1000 * 60 * 60 * 24)
|
||||
) {
|
||||
Log.warn(
|
||||
'It seems like you started an onboarding process, but it has been a while since you last used it. If you want to start a new onboarding process, visit https://expo.new.'
|
||||
);
|
||||
Log.log();
|
||||
}
|
||||
|
||||
const platform =
|
||||
actor.preferences.onboarding.platform === AppPlatform.Android
|
||||
? Platform.ANDROID
|
||||
: Platform.IOS;
|
||||
|
||||
const app = await AppQuery.byIdAsync(graphqlClient, actor.preferences.onboarding.appId);
|
||||
|
||||
const githubUsername = app.githubRepository
|
||||
? app.githubRepository.metadata.githubRepoOwnerName
|
||||
: 'expo';
|
||||
const githubRepositoryName = app.githubRepository
|
||||
? app.githubRepository.metadata.githubRepoName
|
||||
: 'expo-template-default';
|
||||
|
||||
Log.log(`👋 Welcome to Expo, ${actor.username}!`);
|
||||
Log.log();
|
||||
Log.log('✨ We will continue your onboarding process in EAS CLI');
|
||||
Log.log();
|
||||
Log.log(
|
||||
`🚚 Let's start by cloning ${
|
||||
app.githubRepository
|
||||
? `your project (${githubUsername}/${githubRepositoryName})`
|
||||
: `default Expo template project (${githubUsername}/${githubRepositoryName})`
|
||||
} from GitHub and installing dependencies.`
|
||||
);
|
||||
Log.log();
|
||||
let initialTargetProjectDirectory: string;
|
||||
if (targetProjectDirInput) {
|
||||
initialTargetProjectDirectory = targetProjectDirInput;
|
||||
Log.log(`📂 Cloning the project to ${initialTargetProjectDirectory}`);
|
||||
} else {
|
||||
const { selectedTargetProjectDirectory } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'selectedTargetProjectDirectory',
|
||||
message: app.githubRepository
|
||||
? '📂 Where would you like to clone the project to?'
|
||||
: '📂 Where would you like to create your new project directory?',
|
||||
initial: app.githubRepository
|
||||
? path.join(process.cwd(), githubRepositoryName)
|
||||
: path.join(process.cwd(), `${actor.username}-first-project`),
|
||||
});
|
||||
initialTargetProjectDirectory = selectedTargetProjectDirectory;
|
||||
}
|
||||
Log.log();
|
||||
|
||||
const { targetProjectDir: finalTargetProjectDirectory } = await runGitCloneAsync({
|
||||
githubUsername,
|
||||
githubRepositoryName,
|
||||
targetProjectDir: initialTargetProjectDirectory,
|
||||
});
|
||||
|
||||
const vcsClient = new GitClient(finalTargetProjectDirectory);
|
||||
if (!app.githubRepository) {
|
||||
await fs.remove(path.join(finalTargetProjectDirectory, '.git'));
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['init'],
|
||||
});
|
||||
Log.log();
|
||||
await configureProjectFromBareDefaultExpoTemplateAsync({
|
||||
app,
|
||||
vcsClient,
|
||||
targetDir: finalTargetProjectDirectory,
|
||||
});
|
||||
}
|
||||
|
||||
await installDependenciesAsync({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
});
|
||||
const exp = await getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
graphqlClient,
|
||||
actor,
|
||||
});
|
||||
const getDynamicProjectConfigFn = getDynamicPrivateProjectConfigGetter({
|
||||
projectDir: finalTargetProjectDirectory,
|
||||
graphqlClient,
|
||||
actor,
|
||||
});
|
||||
|
||||
if (!app.githubRepository) {
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-updates'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-insights'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'npx',
|
||||
args: ['expo', 'install', 'expo-dev-client'],
|
||||
});
|
||||
Log.log();
|
||||
}
|
||||
await vcsClient.trackFileAsync('package-lock.json');
|
||||
|
||||
const shouldSetupCredentials =
|
||||
((platform === Platform.IOS &&
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Device) ||
|
||||
platform === Platform.ANDROID) &&
|
||||
actor.preferences.onboarding.environment === OnboardingEnvironment.DevBuild;
|
||||
if (shouldSetupCredentials) {
|
||||
Log.log('🔑 Now we need to set up build credentials for your project:');
|
||||
await new SetUpBuildCredentialsCommandAction(
|
||||
actor,
|
||||
graphqlClient,
|
||||
vcsClient,
|
||||
analytics,
|
||||
exp,
|
||||
getDynamicProjectConfigFn,
|
||||
platform,
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
|
||||
? 'development-simulator'
|
||||
: 'development',
|
||||
finalTargetProjectDirectory
|
||||
).runAsync();
|
||||
}
|
||||
|
||||
if (app.githubRepository && (await vcsClient.hasUncommittedChangesAsync())) {
|
||||
Log.log(
|
||||
'📦 We will now commit the changes made by the configuration process and push them to GitHub:'
|
||||
);
|
||||
Log.log();
|
||||
Log.log('🔍 Checking for changes in the repository...');
|
||||
await vcsClient.showChangedFilesAsync();
|
||||
await reviewAndCommitChangesAsync(
|
||||
vcsClient,
|
||||
`[eas-onboarding] Install dependencies${
|
||||
shouldSetupCredentials ? ' and set up build credentials' : ''
|
||||
}`,
|
||||
{ nonInteractive: false }
|
||||
);
|
||||
Log.log('📤 Pushing changes to GitHub...');
|
||||
await runGitPushAsync({
|
||||
targetProjectDir: finalTargetProjectDirectory,
|
||||
});
|
||||
} else if (!app.githubRepository) {
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['add', '.'],
|
||||
});
|
||||
Log.log();
|
||||
await runCommandAsync({
|
||||
cwd: finalTargetProjectDirectory,
|
||||
command: 'git',
|
||||
args: ['commit', '-m', 'Initial commit'],
|
||||
});
|
||||
Log.log();
|
||||
}
|
||||
|
||||
Log.log();
|
||||
Log.log('🎉 We finished configuring your project.');
|
||||
Log.log();
|
||||
if (
|
||||
app.githubRepository ||
|
||||
actor.preferences.onboarding.environment === OnboardingEnvironment.ExpoGo
|
||||
) {
|
||||
Log.log('🚀 You can now go back to the website to continue:');
|
||||
const url = new URL(
|
||||
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
Log.log(`👉 ${link(url)}`);
|
||||
} else {
|
||||
Log.log('🚀 Now we are going to trigger your first build');
|
||||
Log.log();
|
||||
const { buildIds } = await runBuildAndSubmitAsync(
|
||||
graphqlClient,
|
||||
analytics,
|
||||
vcsClient,
|
||||
finalTargetProjectDirectory,
|
||||
{
|
||||
nonInteractive: true,
|
||||
requestedPlatform:
|
||||
platform === Platform.ANDROID ? RequestedPlatform.Android : RequestedPlatform.Ios,
|
||||
profile:
|
||||
actor.preferences.onboarding.deviceType === OnboardingDeviceType.Simulator
|
||||
? 'development-simulator'
|
||||
: 'development',
|
||||
wait: false,
|
||||
clearCache: false,
|
||||
json: false,
|
||||
autoSubmit: false,
|
||||
localBuildOptions: {},
|
||||
freezeCredentials: false,
|
||||
repack: true,
|
||||
},
|
||||
actor,
|
||||
getDynamicProjectConfigFn
|
||||
);
|
||||
const buildId = buildIds[0];
|
||||
Log.log();
|
||||
Log.log('🚀 You can now go back to the website to continue:');
|
||||
const url = new URL(
|
||||
`/onboarding/develop/set-up-project-on-your-machine?project=${app.slug}&accountId=${app.ownerAccount.id}&buildId=${buildId}`,
|
||||
getExpoWebsiteBaseUrl()
|
||||
).toString();
|
||||
Log.log(`👉 ${link(url)}`);
|
||||
}
|
||||
|
||||
const { __typename, ...previousPreferences } = actor.preferences.onboarding;
|
||||
await UserPreferencesMutation.markCliDoneInOnboardingUserPreferencesAsync(graphqlClient, {
|
||||
...previousPreferences,
|
||||
appId: app.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
|
||||
async function getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
}: {
|
||||
projectDir: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
options?: ExpoConfigOptions;
|
||||
}): Promise<CredentialsContextProjectInfo> {
|
||||
const expBefore = getPrivateExpoConfig(projectDir, options);
|
||||
const projectId = await validateOrSetProjectIdAsync({
|
||||
exp: expBefore,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options: {
|
||||
nonInteractive: false,
|
||||
},
|
||||
});
|
||||
const exp = getPrivateExpoConfig(projectDir, options);
|
||||
return {
|
||||
exp,
|
||||
projectId,
|
||||
};
|
||||
}
|
||||
|
||||
// we can't get this automated by using command context because when we run a command the project directory doesn't exist yet
|
||||
function getDynamicPrivateProjectConfigGetter({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
}: {
|
||||
projectDir: string;
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
actor: Actor;
|
||||
}): DynamicConfigContextFn {
|
||||
return async (options?: ExpoConfigOptions) => {
|
||||
return {
|
||||
...(await getPrivateExpoConfigWithProjectIdAsync({
|
||||
projectDir,
|
||||
graphqlClient,
|
||||
actor,
|
||||
options,
|
||||
})),
|
||||
projectDir,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function configureProjectFromBareDefaultExpoTemplateAsync({
|
||||
app,
|
||||
vcsClient,
|
||||
targetDir,
|
||||
}: {
|
||||
app: AppFragment;
|
||||
vcsClient: GitClient;
|
||||
targetDir: string;
|
||||
}): Promise<void> {
|
||||
// Android package name requires each component to start with a lowercase letter.
|
||||
const isUsernameValidSegment = /^[^a-z]/.test(app.ownerAccount.name);
|
||||
const userPrefix = isUsernameValidSegment ? 'user' : '';
|
||||
const isSlugValidSegment = /^[^a-z]/.test(app.slug);
|
||||
const slugPrefix = isSlugValidSegment ? 'app' : '';
|
||||
|
||||
const bundleIdentifier = `com.${userPrefix}${stripInvalidCharactersForBundleIdentifier(
|
||||
app.ownerAccount.name
|
||||
)}.${slugPrefix}${stripInvalidCharactersForBundleIdentifier(app.slug)}`;
|
||||
const updateUrl = getEASUpdateURL(app.id);
|
||||
|
||||
const easBuildGitHubConfig = {
|
||||
android: {
|
||||
image: 'latest',
|
||||
},
|
||||
ios: {
|
||||
image: 'latest',
|
||||
},
|
||||
};
|
||||
|
||||
const easJson: EasJson = {
|
||||
cli: {
|
||||
version: `>= ${easCliVersion}`,
|
||||
},
|
||||
build: {
|
||||
development: {
|
||||
developmentClient: true,
|
||||
distribution: 'internal',
|
||||
channel: 'development',
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
'development-simulator': {
|
||||
extends: 'development',
|
||||
ios: {
|
||||
simulator: true,
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
distribution: 'internal',
|
||||
channel: 'preview',
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
production: {
|
||||
channel: 'production',
|
||||
...easBuildGitHubConfig,
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
|
||||
const easJsonPath = path.join(targetDir, 'eas.json');
|
||||
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
|
||||
await vcsClient.trackFileAsync(easJsonPath);
|
||||
Log.log(
|
||||
`✅ Generated ${chalk.bold('eas.json')}. ${learnMore(
|
||||
'https://docs.expo.dev/build-reference/eas-json/'
|
||||
)}`
|
||||
);
|
||||
Log.log();
|
||||
|
||||
const expoConfig: ExpoConfig = {
|
||||
name: app.name ?? app.slug,
|
||||
slug: app.slug,
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: app.id,
|
||||
},
|
||||
},
|
||||
owner: app.ownerAccount.name,
|
||||
updates: {
|
||||
url: updateUrl,
|
||||
},
|
||||
runtimeVersion: {
|
||||
policy: 'appVersion',
|
||||
},
|
||||
ios: {
|
||||
bundleIdentifier,
|
||||
},
|
||||
android: {
|
||||
package: bundleIdentifier,
|
||||
},
|
||||
};
|
||||
|
||||
const appJsonPath = path.join(targetDir, 'app.json');
|
||||
await fs.writeFile(appJsonPath, `${JSON.stringify({ expo: expoConfig }, null, 2)}\n`);
|
||||
await vcsClient.trackFileAsync(appJsonPath);
|
||||
Log.log(
|
||||
`✅ Generated ${chalk.bold('app.json')}. ${learnMore(
|
||||
'https://docs.expo.dev/versions/latest/config/app/'
|
||||
)}`
|
||||
);
|
||||
Log.log();
|
||||
}
|
||||
|
||||
function stripInvalidCharactersForBundleIdentifier(string: string): string {
|
||||
return string.replaceAll(/[^A-Za-z0-9]/g, '');
|
||||
}
|
||||
@@ -274,6 +274,7 @@ function mockTestProject({
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -367,6 +367,7 @@ function mockTestProject({
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -259,6 +259,7 @@ function mockTestProject({
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: '1234',
|
||||
slug: 'testing-123',
|
||||
name: 'testing-123',
|
||||
fullName: '@jester/testing-123',
|
||||
ownerAccount: jester.accounts[0],
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@ export default class UpdateConfigure extends EasCommand {
|
||||
projectDir,
|
||||
platform,
|
||||
vcsClient,
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
await ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Platform as PublishPlatform } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
@@ -6,7 +7,6 @@ import nullthrows from 'nullthrows';
|
||||
import { ensureBranchExistsAsync } from '../../branch/queries';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import { getUpdateGroupUrl } from '../../build/utils/url';
|
||||
import { ensureChannelExistsAsync } from '../../channel/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
import { getPaginatedQueryOptions } from '../../commandUtils/pagination';
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
resolveInputDirectoryAsync,
|
||||
uploadAssetsAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
|
||||
import { getUpdateJsonInfosForUpdates } from '../../update/utils';
|
||||
import {
|
||||
@@ -199,6 +200,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
projectDir,
|
||||
projectId,
|
||||
vcsClient,
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
const { exp } = await getDynamicPublicProjectConfigAsync();
|
||||
@@ -359,29 +361,24 @@ export default class UpdatePublish extends EasCommand {
|
||||
throw e;
|
||||
}
|
||||
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync({
|
||||
exp,
|
||||
platforms: realizedPlatforms,
|
||||
projectDir,
|
||||
vcsClient,
|
||||
workflows: {
|
||||
...workflows,
|
||||
web: Workflow.UNKNOWN,
|
||||
},
|
||||
env: undefined,
|
||||
});
|
||||
const runtimeToPlatformMapping =
|
||||
getRuntimeToPlatformMappingFromRuntimeVersions(runtimeVersions);
|
||||
|
||||
const { branchId, createdBranch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
});
|
||||
if (createdBranch) {
|
||||
await ensureChannelExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchId,
|
||||
channelName: branchName,
|
||||
});
|
||||
Log.withTick(
|
||||
`Channel: ${chalk.bold(branchName)} pointed at branch: ${chalk.bold(branchName)}`
|
||||
);
|
||||
}
|
||||
|
||||
const gitCommitHash = await vcsClient.getCommitHashAsync();
|
||||
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
|
||||
@@ -517,7 +514,7 @@ export default class UpdatePublish extends EasCommand {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: 'Website link', value: updateGroupLink },
|
||||
{ label: 'EAS Dashboard', value: updateGroupLink },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Platform as PublishPlatform } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Errors, Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { ensureBranchExistsAsync } from '../../branch/queries';
|
||||
import { getUpdateGroupUrl } from '../../build/utils/url';
|
||||
import { ensureChannelExistsAsync } from '../../channel/queries';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
getRuntimeVersionObjectAsync,
|
||||
getUpdateMessageForCommandAsync,
|
||||
} from '../../project/publish';
|
||||
import { resolveWorkflowPerPlatformAsync } from '../../project/workflow';
|
||||
import { ensureEASUpdateIsConfiguredAsync } from '../../update/configure';
|
||||
import { getUpdateJsonInfosForUpdates } from '../../update/utils';
|
||||
import {
|
||||
@@ -154,6 +154,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
projectDir,
|
||||
projectId,
|
||||
vcsClient,
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
// check that the expo-updates package version supports roll back to embedded
|
||||
@@ -184,28 +185,24 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
const realizedPlatforms: PublishPlatform[] =
|
||||
platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
|
||||
const { branchId, createdBranch } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
const { branchId } = await ensureBranchExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchName,
|
||||
});
|
||||
if (createdBranch) {
|
||||
await ensureChannelExistsAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
branchId,
|
||||
channelName: branchName,
|
||||
});
|
||||
}
|
||||
|
||||
Log.withTick(`Channel: ${chalk.bold(branchName)} pointed at branch: ${chalk.bold(branchName)}`);
|
||||
|
||||
const gitCommitHash = await vcsClient.getCommitHashAsync();
|
||||
const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync();
|
||||
|
||||
const workflows = await resolveWorkflowPerPlatformAsync(projectDir, vcsClient);
|
||||
const runtimeVersions = await getRuntimeVersionObjectAsync({
|
||||
exp,
|
||||
platforms: realizedPlatforms,
|
||||
projectDir,
|
||||
vcsClient,
|
||||
workflows: {
|
||||
...workflows,
|
||||
web: Workflow.UNKNOWN,
|
||||
},
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups'];
|
||||
@@ -279,7 +276,7 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: 'Website link', value: updateGroupLink },
|
||||
{ label: 'EAS Dashboard', value: updateGroupLink },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
@@ -55,6 +55,7 @@ export const testPKCS12EmptyPasswordKeystore: Keystore = {
|
||||
export const testAppFragment: AppFragment = {
|
||||
id: 'test-app-id',
|
||||
fullName: '@testuser/testapp',
|
||||
name: 'testapp',
|
||||
slug: 'testapp',
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AppStoreApi from '../ios/appstore/AppStoreApi';
|
||||
import { AuthCtx } from '../ios/appstore/authenticateTypes';
|
||||
import { AuthCtx, AuthenticationMode } from '../ios/appstore/authenticateTypes';
|
||||
|
||||
export const testAuthCtx: AuthCtx = {
|
||||
appleId: 'test-apple-id',
|
||||
@@ -9,6 +9,7 @@ export const testAuthCtx: AuthCtx = {
|
||||
|
||||
export function getAppstoreMock(): AppStoreApi {
|
||||
return {
|
||||
defaultAuthenticationMode: AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(),
|
||||
ensureBundleIdExistsAsync: jest.fn(),
|
||||
listDistributionCertificatesAsync: jest.fn(),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import assert from 'node:assert';
|
||||
|
||||
import { AppFragment, Role } from '../../graphql/generated';
|
||||
import { Actor } from '../../user/User';
|
||||
|
||||
@@ -24,6 +26,7 @@ export const jester: Actor = {
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
preferences: {},
|
||||
};
|
||||
|
||||
export const jester2 = {
|
||||
@@ -41,6 +44,7 @@ export const jester2 = {
|
||||
featureGates: {},
|
||||
};
|
||||
|
||||
assert(jester.__typename === 'User');
|
||||
export const testUsername = jester.username;
|
||||
export const testSlug = 'testApp';
|
||||
export const testProjectId = '7ef93448-3bc7-4b57-be32-99326dcf24f0';
|
||||
@@ -76,6 +80,7 @@ export const testAppJsonWithDifferentOwner = {
|
||||
export const testAppQueryByIdResponse: AppFragment = {
|
||||
id: testProjectId,
|
||||
slug: testSlug,
|
||||
name: 'testing 123',
|
||||
fullName: testExperienceName,
|
||||
ownerAccount: jester.accounts[0],
|
||||
};
|
||||
@@ -55,6 +55,7 @@ export const testAppleAppIdentifierFragment: AppleAppIdentifierFragment = {
|
||||
export const testAppFragment: AppFragment = {
|
||||
id: 'test-app-id',
|
||||
fullName: '@testuser/testapp',
|
||||
name: 'testapp',
|
||||
slug: 'testapp',
|
||||
ownerAccount: {
|
||||
id: 'test-account-id',
|
||||
|
||||
+60
-7
@@ -4,27 +4,76 @@ import gql from 'graphql-tag';
|
||||
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
GoogleServiceAccountKeyByAccountQuery,
|
||||
GoogleServiceAccountKeyFragment,
|
||||
GoogleServiceAccountKeysPaginatedByAccountQuery,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { GoogleServiceAccountKeyFragmentNode } from '../../../../../graphql/types/credentials/GoogleServiceAccountKey';
|
||||
import { Connection, QueryParams, fetchEntireDatasetAsync } from '../../../../../utils/relay';
|
||||
|
||||
export const GoogleServiceAccountKeyQuery = {
|
||||
async getAllForAccountAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string
|
||||
): Promise<GoogleServiceAccountKeyFragment[]> {
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<GoogleServiceAccountKeyFragment>> => {
|
||||
return await GoogleServiceAccountKeyQuery.getAllForAccountPaginatedAsync(
|
||||
graphqlClient,
|
||||
accountName,
|
||||
relayArgs
|
||||
);
|
||||
};
|
||||
return await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
progressBarLabel: 'fetching Google Service Account Keys...',
|
||||
});
|
||||
},
|
||||
async getAllForAccountPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
{
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
}: { after?: string; first?: number; before?: string; last?: number }
|
||||
): Promise<
|
||||
GoogleServiceAccountKeysPaginatedByAccountQuery['account']['byName']['googleServiceAccountKeysPaginated']
|
||||
> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GoogleServiceAccountKeyByAccountQuery>(
|
||||
.query<GoogleServiceAccountKeysPaginatedByAccountQuery>(
|
||||
gql`
|
||||
query GoogleServiceAccountKeyByAccountQuery($accountName: String!) {
|
||||
query GoogleServiceAccountKeysPaginatedByAccountQuery(
|
||||
$accountName: String!
|
||||
$after: String
|
||||
$first: Int
|
||||
$before: String
|
||||
$last: Int
|
||||
) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
googleServiceAccountKeys {
|
||||
id
|
||||
...GoogleServiceAccountKeyFragment
|
||||
googleServiceAccountKeysPaginated(
|
||||
after: $after
|
||||
first: $first
|
||||
before: $before
|
||||
last: $last
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...GoogleServiceAccountKeyFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +82,10 @@ export const GoogleServiceAccountKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['GoogleServiceAccountKey'],
|
||||
@@ -40,6 +93,6 @@ export const GoogleServiceAccountKeyQuery = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.googleServiceAccountKeys;
|
||||
return data.account.byName.googleServiceAccountKeysPaginated;
|
||||
},
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import AppStoreApi from './ios/appstore/AppStoreApi';
|
||||
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
|
||||
import { Analytics } from '../analytics/AnalyticsManager';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppStoreConnectApiKeyFragment, AppleTeamType } from '../graphql/generated';
|
||||
import Log from '../log';
|
||||
import { getPrivateExpoConfig } from '../project/expoConfig';
|
||||
import { confirmAsync } from '../prompts';
|
||||
@@ -25,6 +26,7 @@ export class CredentialsContext {
|
||||
public readonly appStore = new AppStoreApi();
|
||||
public readonly ios = IosGraphqlClient;
|
||||
public readonly nonInteractive: boolean;
|
||||
public readonly freezeCredentials: boolean = false;
|
||||
public readonly projectDir: string;
|
||||
public readonly user: Actor;
|
||||
public readonly graphqlClient: ExpoGraphqlClient;
|
||||
@@ -47,6 +49,7 @@ export class CredentialsContext {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
analytics: Analytics;
|
||||
vcsClient: Client;
|
||||
freezeCredentials?: boolean;
|
||||
env?: Env;
|
||||
}
|
||||
) {
|
||||
@@ -58,6 +61,7 @@ export class CredentialsContext {
|
||||
this.vcsClient = options.vcsClient;
|
||||
this.nonInteractive = options.nonInteractive ?? false;
|
||||
this.projectInfo = options.projectInfo;
|
||||
this.freezeCredentials = options.freezeCredentials ?? false;
|
||||
}
|
||||
|
||||
get hasProjectContext(): boolean {
|
||||
@@ -82,6 +86,21 @@ export class CredentialsContext {
|
||||
getPrivateExpoConfig(this.options.projectDir);
|
||||
}
|
||||
|
||||
public configureAppStoreAuth(ascApiKey: AppStoreConnectApiKeyFragment): void {
|
||||
this.appStore.authCtx = {
|
||||
ascApiKey: {
|
||||
keyP8: ascApiKey.keyP8,
|
||||
keyId: ascApiKey.keyIdentifier,
|
||||
issuerId: ascApiKey.issuerIdentifier,
|
||||
},
|
||||
team: {
|
||||
id: ascApiKey.appleTeam?.appleTeamIdentifier ?? '',
|
||||
name: ascApiKey.appleTeam?.appleTeamName ?? undefined,
|
||||
inHouse: ascApiKey.appleTeam?.appleTeamType === AppleTeamType.InHouse ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
|
||||
if (this.appStore.authCtx || !this.shouldAskAuthenticateAppStore) {
|
||||
// skip prompts if already have apple ctx or already asked about it
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
import { learnMore } from '../log';
|
||||
|
||||
export class MissingCredentialsNonInteractiveError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message ?? 'Credentials are not set up. Run this command again in interactive mode.');
|
||||
}
|
||||
}
|
||||
|
||||
export class InsufficientAuthenticationNonInteractiveError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(
|
||||
message ??
|
||||
`Authentication with an ASC API key is required in non-interactive mode. ${learnMore(
|
||||
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbidCredentialModificationError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(
|
||||
message ??
|
||||
'Credentials cannot be modified. Run this command again without the --freeze-credentials flag.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingCredentialsError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message ?? 'Credentials are not set up.');
|
||||
|
||||
@@ -14,6 +14,7 @@ import { isAdHocProfile, isEnterpriseUniversalProfile } from './utils/provisioni
|
||||
import { CommonIosAppCredentialsFragment } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { isExpoNotificationsInstalled } from '../../project/projectUtils';
|
||||
import { selectAsync } from '../../prompts';
|
||||
import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
@@ -105,6 +106,11 @@ export default class IosCredentialsProvider {
|
||||
|
||||
if (ctx.easJsonCliConfig?.promptToConfigurePushNotifications === false) {
|
||||
return null;
|
||||
} else if (
|
||||
ctx.easJsonCliConfig?.promptToConfigurePushNotifications === undefined &&
|
||||
!(await isExpoNotificationsInstalled(ctx.projectDir))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const setupOption = await selectAsync(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UserRole } from '@expo/apple-utils';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -24,10 +25,19 @@ import { isAscApiKeyValidAndTrackedAsync } from '../validators/validateAscApiKey
|
||||
|
||||
export enum AppStoreApiKeyPurpose {
|
||||
SUBMISSION_SERVICE = 'EAS Submit',
|
||||
BUILD_SERVICE = 'EAS Build',
|
||||
}
|
||||
|
||||
export async function promptForAscApiKeyPathAsync(ctx: CredentialsContext): Promise<AscApiKeyPath> {
|
||||
const { keyId, keyP8Path } = await promptForKeyP8AndIdAsync();
|
||||
export const PURPOSE_TO_ROLES: Record<AppStoreApiKeyPurpose, UserRole[]> = {
|
||||
[AppStoreApiKeyPurpose.SUBMISSION_SERVICE]: [UserRole.ADMIN],
|
||||
[AppStoreApiKeyPurpose.BUILD_SERVICE]: [UserRole.ADMIN],
|
||||
};
|
||||
|
||||
export async function promptForAscApiKeyPathAsync(
|
||||
ctx: CredentialsContext,
|
||||
roles: UserRole[]
|
||||
): Promise<AscApiKeyPath> {
|
||||
const { keyId, keyP8Path } = await promptForKeyP8AndIdAsync(roles);
|
||||
|
||||
const bestEffortIssuerId = await getBestEffortIssuerIdAsync(ctx, keyId);
|
||||
if (bestEffortIssuerId) {
|
||||
@@ -60,15 +70,15 @@ export async function getMinimalAscApiKeyAsync(ascApiKey: AscApiKey): Promise<Mi
|
||||
|
||||
export async function provideOrGenerateAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
roles: UserRole[]
|
||||
): Promise<MinimalAscApiKey> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const userProvided = await promptForAscApiKeyAsync(ctx);
|
||||
const userProvided = await promptForAscApiKeyAsync(ctx, roles);
|
||||
if (!userProvided) {
|
||||
return await generateAscApiKeyAsync(ctx, purpose);
|
||||
return await generateAscApiKeyAsync(ctx, roles);
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
@@ -86,39 +96,49 @@ export async function provideOrGenerateAscApiKeyAsync(
|
||||
if (useUserProvided) {
|
||||
return userProvided;
|
||||
}
|
||||
return await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
||||
return await provideOrGenerateAscApiKeyAsync(ctx, roles);
|
||||
}
|
||||
|
||||
async function generateAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
roles: UserRole[]
|
||||
): Promise<MinimalAscApiKey> {
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
const ascApiKey = await ctx.appStore.createAscApiKeyAsync(ctx.analytics, {
|
||||
nickname: getAscApiKeyName(purpose),
|
||||
nickname: getAscApiKeyName(),
|
||||
roles,
|
||||
});
|
||||
return await getMinimalAscApiKeyAsync(ascApiKey);
|
||||
}
|
||||
|
||||
export function getAscApiKeyName(purpose: AppStoreApiKeyPurpose): string {
|
||||
const nameParts = ['[Expo]', purpose, nanoid(10)];
|
||||
export function getAscApiKeyName(): string {
|
||||
const nameParts = ['[Expo]', nanoid(10)];
|
||||
return nameParts.join(' ');
|
||||
}
|
||||
|
||||
async function promptForAscApiKeyAsync(ctx: CredentialsContext): Promise<MinimalAscApiKey | null> {
|
||||
async function promptForAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
roles: UserRole[]
|
||||
): Promise<MinimalAscApiKey | null> {
|
||||
const shouldAutoGenerateCredentials = await shouldAutoGenerateCredentialsAsync(ascApiKeyIdSchema);
|
||||
if (shouldAutoGenerateCredentials) {
|
||||
return null;
|
||||
}
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, roles);
|
||||
const { keyP8Path, keyId, issuerId } = ascApiKeyPath;
|
||||
return { keyP8: await fs.readFile(keyP8Path, 'utf-8'), keyId, issuerId };
|
||||
}
|
||||
|
||||
async function promptForKeyP8AndIdAsync(): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
||||
async function promptForKeyP8AndIdAsync(
|
||||
roles: UserRole[]
|
||||
): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
||||
const rolesStringified = roles.map(r => r.toString()).join(', ');
|
||||
const isPlural = roles.length > 1;
|
||||
Log.log(
|
||||
chalk.bold(
|
||||
'An App Store Connect Api key is required to upload your app to the Apple App Store Connect'
|
||||
`An App Store Connect Api key with the ${rolesStringified} role${
|
||||
isPlural ? 's' : ''
|
||||
} is required to upload your app to the Apple App Store Connect`
|
||||
)
|
||||
);
|
||||
Log.log(
|
||||
@@ -241,13 +261,16 @@ export function sortAscApiKeysByUpdatedAtDesc(
|
||||
}
|
||||
|
||||
export function formatAscApiKey(ascApiKey: AppStoreConnectApiKeyFragment): string {
|
||||
const { keyIdentifier, appleTeam, name, updatedAt } = ascApiKey;
|
||||
const { keyIdentifier, appleTeam, roles, name, updatedAt } = ascApiKey;
|
||||
let line: string = '';
|
||||
line += `Key ID: ${keyIdentifier}`;
|
||||
|
||||
if (name) {
|
||||
line += chalk.gray(`\n Name: ${name}`);
|
||||
}
|
||||
if (roles && roles.length > 0) {
|
||||
line += chalk.gray(`\n Roles: ${roles.join(', ')}`);
|
||||
}
|
||||
if (appleTeam) {
|
||||
line += chalk.gray(`\n ${formatAppleTeam(appleTeam)}`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||
import { ChooseAssociatedAppleTeamId } from './ChooseAssociatedAppleTeamId';
|
||||
import { UpdateAppleTeamInfo } from './UpdateAppleTeamInfo';
|
||||
import {
|
||||
AppStoreConnectApiKeyFragment,
|
||||
AppleTeamFragment,
|
||||
CommonIosAppCredentialsFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
@@ -16,12 +18,15 @@ export class AssignAscApiKey {
|
||||
ascApiKey: AppStoreConnectApiKeyFragment,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<CommonIosAppCredentialsFragment> {
|
||||
const appleTeam =
|
||||
(await resolveAppleTeamIfAuthenticatedAsync(ctx, this.app)) ?? ascApiKey.appleTeam ?? null;
|
||||
const [ascApiKeyWithAppleTeam, appleTeam] = await this.ensureKeyHasAppleTeamInfoAsync(
|
||||
ctx,
|
||||
ascApiKey
|
||||
);
|
||||
|
||||
const appCredentials = await ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync(
|
||||
ctx.graphqlClient,
|
||||
this.app,
|
||||
{ appleTeam: appleTeam ?? undefined }
|
||||
{ appleTeam }
|
||||
);
|
||||
let updatedAppCredentials;
|
||||
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
@@ -29,7 +34,15 @@ export class AssignAscApiKey {
|
||||
ctx.graphqlClient,
|
||||
appCredentials,
|
||||
{
|
||||
ascApiKeyIdForSubmissions: ascApiKey.id,
|
||||
ascApiKeyIdForSubmissions: ascApiKeyWithAppleTeam.id,
|
||||
}
|
||||
);
|
||||
} else if (purpose === AppStoreApiKeyPurpose.BUILD_SERVICE) {
|
||||
updatedAppCredentials = await ctx.ios.updateIosAppCredentialsAsync(
|
||||
ctx.graphqlClient,
|
||||
appCredentials,
|
||||
{
|
||||
ascApiKeyIdForBuilds: ascApiKeyWithAppleTeam.id,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@@ -40,4 +53,28 @@ export class AssignAscApiKey {
|
||||
);
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
|
||||
private async ensureKeyHasAppleTeamInfoAsync(
|
||||
ctx: CredentialsContext,
|
||||
ascApiKey: AppStoreConnectApiKeyFragment
|
||||
): Promise<[AppStoreConnectApiKeyFragment, AppleTeamFragment]> {
|
||||
let appleTeamIdentifier = ascApiKey.appleTeam?.appleTeamIdentifier;
|
||||
if (!appleTeamIdentifier) {
|
||||
const chooseAssociatedAppleTeamIdAction = new ChooseAssociatedAppleTeamId(
|
||||
'App Store Connect API Key'
|
||||
);
|
||||
appleTeamIdentifier = await chooseAssociatedAppleTeamIdAction.runAsync(ctx);
|
||||
}
|
||||
const updateAppleTeamAction = new UpdateAppleTeamInfo(this.app.account, appleTeamIdentifier);
|
||||
const appleTeam = await updateAppleTeamAction.runAsync(ctx);
|
||||
if (!ascApiKey.appleTeam) {
|
||||
const updatedAscApiKey = await ctx.ios.updateAscApiKeyAsync(
|
||||
ctx.graphqlClient,
|
||||
ascApiKey,
|
||||
appleTeam
|
||||
);
|
||||
return [updatedAscApiKey, appleTeam];
|
||||
}
|
||||
return [ascApiKey, appleTeam];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Teams } from '@expo/apple-utils';
|
||||
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
|
||||
export class ChooseAssociatedAppleTeamId {
|
||||
constructor(private associatedCredential: string) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<string> {
|
||||
if (!ctx.appStore.authCtx) {
|
||||
return await this.promptForAppleTeamIdAsync();
|
||||
}
|
||||
|
||||
const teams = await Teams.getTeamsAsync();
|
||||
const chosenTeamId = await this.promptForAppleTeamIdFromDeveloperPortalAsync(
|
||||
teams.map(team => ({ prettyName: `${team.name} (${team.type})`, teamId: team.teamId }))
|
||||
);
|
||||
return chosenTeamId ?? (await this.promptForAppleTeamIdAsync());
|
||||
}
|
||||
|
||||
private async promptForAppleTeamIdFromDeveloperPortalAsync(
|
||||
teamChoices: { prettyName: string; teamId: string }[]
|
||||
): Promise<string | null> {
|
||||
if (teamChoices.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const { appleTeamId } = await promptAsync({
|
||||
type: 'select',
|
||||
message: `Select the Apple Team associated with your ${this.associatedCredential}:`,
|
||||
name: 'appleTeamId',
|
||||
choices: [
|
||||
...teamChoices.map(({ prettyName, teamId }) => ({ title: prettyName, value: teamId })),
|
||||
{ title: 'None of the above', value: null },
|
||||
],
|
||||
});
|
||||
return appleTeamId;
|
||||
}
|
||||
|
||||
private async promptForAppleTeamIdAsync(): Promise<string> {
|
||||
const { appleTeamId } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'appleTeamId',
|
||||
message: `Input the Apple Team ID associated with your ${this.associatedCredential}:`,
|
||||
validate: (val: string) => val !== '',
|
||||
});
|
||||
return appleTeamId;
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,18 @@ import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { getApplePlatformFromTarget } from '../../../project/ios/target';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../errors';
|
||||
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
import { AuthCtx } from '../appstore/authenticateTypes';
|
||||
import { AuthCtx, AuthenticationMode } from '../appstore/authenticateTypes';
|
||||
import { Target } from '../types';
|
||||
|
||||
export class ConfigureProvisioningProfile {
|
||||
@@ -27,9 +30,18 @@ export class ConfigureProvisioningProfile {
|
||||
public async runAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleProvisioningProfileMutationResult | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Configuring Provisioning Profiles is only supported in interactive mode.'
|
||||
if (ctx.freezeCredentials) {
|
||||
throw new ForbidCredentialModificationError(
|
||||
'Remove the --freeze-credentials flag to configure a Provisioning Profile.'
|
||||
);
|
||||
} else if (
|
||||
ctx.nonInteractive &&
|
||||
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
|
||||
) {
|
||||
throw new InsufficientAuthenticationNonInteractiveError(
|
||||
`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
|
||||
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const { developerPortalIdentifier, provisioningProfile } = this.originalProvisioningProfile;
|
||||
@@ -46,7 +58,7 @@ export class ConfigureProvisioningProfile {
|
||||
return null;
|
||||
}
|
||||
|
||||
const applePlatform = await getApplePlatformFromTarget(this.target);
|
||||
const applePlatform = getApplePlatformFromTarget(this.target);
|
||||
const profilesFromApple = await ctx.appStore.listProvisioningProfilesAsync(
|
||||
this.app.bundleIdentifier,
|
||||
applePlatform
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { AppStoreApiKeyPurpose, provideOrGenerateAscApiKeyAsync } from './AscApiKeyUtils';
|
||||
import { AccountFragment, AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||
import {
|
||||
AppStoreApiKeyPurpose,
|
||||
PURPOSE_TO_ROLES,
|
||||
provideOrGenerateAscApiKeyAsync,
|
||||
} from './AscApiKeyUtils';
|
||||
import { ChooseAssociatedAppleTeamId } from './ChooseAssociatedAppleTeamId';
|
||||
import { UpdateAppleTeamInfo } from './UpdateAppleTeamInfo';
|
||||
import {
|
||||
AccountFragment,
|
||||
AppStoreConnectApiKeyFragment,
|
||||
AppleTeamFragment,
|
||||
} from '../../../graphql/generated';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MinimalAscApiKey } from '../credentials';
|
||||
|
||||
export class CreateAscApiKey {
|
||||
constructor(private account: AccountFragment) {}
|
||||
@@ -12,8 +23,32 @@ export class CreateAscApiKey {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
const roles = PURPOSE_TO_ROLES[purpose];
|
||||
if (!roles) {
|
||||
throw new Error(`Unsupported purpose: ${purpose}`);
|
||||
}
|
||||
|
||||
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
||||
return await ctx.ios.createAscApiKeyAsync(ctx.graphqlClient, this.account, ascApiKey);
|
||||
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, roles);
|
||||
const appleTeam = await this.ensureKeyHasAppleTeamInfoAsync(ctx, ascApiKey);
|
||||
|
||||
return await ctx.ios.createAscApiKeyAsync(ctx.graphqlClient, this.account, {
|
||||
...ascApiKey,
|
||||
teamId: appleTeam.appleTeamIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
private async ensureKeyHasAppleTeamInfoAsync(
|
||||
ctx: CredentialsContext,
|
||||
ascApiKey: MinimalAscApiKey
|
||||
): Promise<AppleTeamFragment> {
|
||||
let appleTeamIdentifier = ascApiKey.teamId;
|
||||
if (!appleTeamIdentifier) {
|
||||
const chooseAssociatedAppleTeamIdAction = new ChooseAssociatedAppleTeamId(
|
||||
'App Store Connect API Key'
|
||||
);
|
||||
appleTeamIdentifier = await chooseAssociatedAppleTeamIdAction.runAsync(ctx);
|
||||
}
|
||||
const updateAppleTeamAction = new UpdateAppleTeamInfo(this.account, appleTeamIdentifier);
|
||||
return await updateAppleTeamAction.runAsync(ctx);
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,17 @@ import nullthrows from 'nullthrows';
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { generateProvisioningProfileAsync } from './ProvisioningProfileUtils';
|
||||
import { AppleDistributionCertificateFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../errors';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { ProvisioningProfile } from '../appstore/Credentials.types';
|
||||
import { AuthCtx } from '../appstore/authenticateTypes';
|
||||
import { AuthCtx, AuthenticationMode } from '../appstore/authenticateTypes';
|
||||
import { provisioningProfileSchema } from '../credentials';
|
||||
import { Target } from '../types';
|
||||
|
||||
@@ -23,9 +26,18 @@ export class CreateProvisioningProfile {
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AppleProvisioningProfileMutationResult> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Creating Provisioning Profiles is only supported in interactive mode.'
|
||||
if (ctx.freezeCredentials) {
|
||||
throw new ForbidCredentialModificationError(
|
||||
'Run this command again without the --freeze-credentials flag in order to generate a new Provisioning Profile.'
|
||||
);
|
||||
} else if (
|
||||
ctx.nonInteractive &&
|
||||
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
|
||||
) {
|
||||
throw new InsufficientAuthenticationNonInteractiveError(
|
||||
`In order to generate a new Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
|
||||
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const appleAuthCtx = await ctx.appStore.ensureAuthenticatedAsync();
|
||||
@@ -49,16 +61,31 @@ export class CreateProvisioningProfile {
|
||||
return provisioningProfileMutationResult;
|
||||
}
|
||||
|
||||
private async provideOrGenerateAsync(
|
||||
ctx: CredentialsContext,
|
||||
appleAuthCtx: AuthCtx
|
||||
): Promise<ProvisioningProfile> {
|
||||
private async maybeGetUserProvidedAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<ProvisioningProfile | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
return null;
|
||||
}
|
||||
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
|
||||
if (userProvided) {
|
||||
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
|
||||
Log.warn('Provisioning profile: Unable to validate specified profile.');
|
||||
return userProvided;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async provideOrGenerateAsync(
|
||||
ctx: CredentialsContext,
|
||||
appleAuthCtx: AuthCtx
|
||||
): Promise<ProvisioningProfile> {
|
||||
const maybeUserProvided = await this.maybeGetUserProvidedAsync(ctx);
|
||||
if (maybeUserProvided) {
|
||||
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
|
||||
Log.warn('Provisioning profile: Unable to validate specified profile.');
|
||||
return maybeUserProvided;
|
||||
}
|
||||
assert(
|
||||
this.distributionCertificate.certificateP12,
|
||||
'Distribution Certificate must have p12 certificate'
|
||||
|
||||
@@ -48,8 +48,13 @@ export function formatDistributionCertificate(
|
||||
buildCredentials => buildCredentials.iosAppCredentials.app
|
||||
);
|
||||
if (apps.length) {
|
||||
const appFullNames = apps.map(app => app.fullName).join(',');
|
||||
line += chalk.gray(`\n 📲 Used by: ${appFullNames}`);
|
||||
// iosAppBuildCredentialsList is capped at 20 on www
|
||||
const appFullNames = apps
|
||||
.map(app => app.fullName)
|
||||
.slice(0, 19)
|
||||
.join(',');
|
||||
const andMaybeMore = apps.length > 19 ? ' (and more)' : '';
|
||||
line += chalk.gray(`\n 📲 Used by: ${appFullNames}${andMaybeMore}`);
|
||||
}
|
||||
|
||||
if (validSerialNumbers?.includes(serialNumber)) {
|
||||
@@ -254,6 +259,10 @@ function formatDistributionCertificateFromApple(
|
||||
const { name, status, id, expires, created, ownerName, serialNumber } = appleInfo;
|
||||
const expiresDate = new Date(expires * 1000).toDateString();
|
||||
const createdDate = new Date(created * 1000).toDateString();
|
||||
return `${name} (${status}) - Cert ID: ${id}, Serial number: ${serialNumber}, Team ID: ${appleInfo.ownerId}, Team name: ${ownerName}
|
||||
expires: ${expiresDate}, created: ${createdDate}`;
|
||||
return [
|
||||
`🍏 ${chalk.bold(name)} (${status})`,
|
||||
`${chalk.bold('ID:')} ${id} ${chalk.bold('Serial Number:')} ${serialNumber}`,
|
||||
`${chalk.bold('Apple Team:')} ${appleInfo.ownerId} (${ownerName})`,
|
||||
`${chalk.bold('Expires:')} ${expiresDate} ${chalk.bold('Created:')} ${createdDate}`,
|
||||
].join('\n\t');
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
|
||||
export class GetAscApiKey {
|
||||
static async getForBuildServiceAsync(
|
||||
app: AppLookupParams,
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppStoreConnectApiKeyFragment | null> {
|
||||
const appCredentials = await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(
|
||||
ctx.graphqlClient,
|
||||
app
|
||||
);
|
||||
const ascApiKey = appCredentials?.appStoreConnectApiKeyForBuilds;
|
||||
if (!ascApiKey) {
|
||||
return null;
|
||||
}
|
||||
const appleTeam = ascApiKey.appleTeam;
|
||||
const appleTeamIdentifier = appleTeam?.appleTeamIdentifier;
|
||||
const appleTeamType = appleTeam?.appleTeamType;
|
||||
const hasAppleTeamInfo = !!appleTeamIdentifier && !!appleTeamType;
|
||||
if (!hasAppleTeamInfo) {
|
||||
Log.warn(
|
||||
`App Store Connect API Key for EAS Build is missing Apple Team information. Run "eas credentials" and select "Use an existing API Key" to update the missing information.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return ascApiKey;
|
||||
}
|
||||
}
|
||||
@@ -185,8 +185,13 @@ export function formatPushKey(
|
||||
|
||||
const apps = pushKey.iosAppCredentialsList.map(appCredentials => appCredentials.app);
|
||||
if (apps.length) {
|
||||
const appFullNames = apps.map(app => app.fullName).join(',');
|
||||
line += chalk.gray(`\n 📲 Used by: ${appFullNames}`);
|
||||
// iosAppCredentialsList is capped at 20 on www
|
||||
const appFullNames = apps
|
||||
.map(app => app.fullName)
|
||||
.slice(0, 19)
|
||||
.join(',');
|
||||
const andMaybeMore = apps.length > 19 ? ' (and more)' : '';
|
||||
line += chalk.gray(`\n 📲 Used by: ${appFullNames}${andMaybeMore}`);
|
||||
}
|
||||
|
||||
if (validPushKeyIdentifiers?.includes(keyIdentifier)) {
|
||||
|
||||
@@ -34,14 +34,20 @@ export class RemoveDistributionCertificate {
|
||||
buildCredentials => buildCredentials.iosAppCredentials.app
|
||||
);
|
||||
if (apps.length !== 0) {
|
||||
const appFullNames = apps.map(app => app.fullName).join(',');
|
||||
// iosAppBuildCredentialsList is capped at 20 on www
|
||||
const appFullNames = apps
|
||||
.map(app => app.fullName)
|
||||
.slice(0, 19)
|
||||
.join(',');
|
||||
const andMaybeMore = apps.length > 19 ? ' (and more)' : '';
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
`Certificate is currently used by ${appFullNames} and cannot be deleted in non-interactive mode.`
|
||||
`Certificate is currently used by ${appFullNames}${andMaybeMore} and cannot be deleted in non-interactive mode.`
|
||||
);
|
||||
}
|
||||
const confirm = await confirmAsync({
|
||||
message: `You are removing certificate used by ${appFullNames}. Do you want to continue?`,
|
||||
message: `You are removing certificate used by ${appFullNames}${andMaybeMore}. Do you want to continue?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
Log.log('Aborting');
|
||||
|
||||
@@ -27,9 +27,14 @@ export class RemovePushKey {
|
||||
|
||||
const apps = this.pushKey.iosAppCredentialsList.map(appCredentials => appCredentials.app);
|
||||
if (apps.length !== 0) {
|
||||
const appFullNames = apps.map(app => app.fullName).join(',');
|
||||
// iosAppCredentialsList is capped at 20 on www
|
||||
const appFullNames = apps
|
||||
.map(app => app.fullName)
|
||||
.slice(0, 19)
|
||||
.join(',');
|
||||
const andMaybeMore = apps.length > 19 ? ' (and more)' : '';
|
||||
const confirm = await confirmAsync({
|
||||
message: `Removing this push key will disable push notifications for ${appFullNames}. Do you want to continue?`,
|
||||
message: `Removing this push key will disable push notifications for ${appFullNames}${andMaybeMore}. Do you want to continue?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
Log.log('Aborting');
|
||||
|
||||
@@ -110,10 +110,13 @@ export class SetUpAscApiKey {
|
||||
ctx.graphqlClient,
|
||||
this.app
|
||||
);
|
||||
if (purpose !== AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
||||
} else if (purpose === AppStoreApiKeyPurpose.BUILD_SERVICE) {
|
||||
return !!appCredentials?.appStoreConnectApiKeyForBuilds;
|
||||
} else {
|
||||
throw new Error(`App Store Connect API Key setup is not yet supported for ${purpose}.`);
|
||||
}
|
||||
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
||||
}
|
||||
|
||||
private async processChoicesAsync(
|
||||
|
||||
@@ -15,12 +15,17 @@ import {
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
} from '../../../graphql/generated';
|
||||
import { learnMore } from '../../../log';
|
||||
import { getApplePlatformFromTarget } from '../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../errors';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
import { AuthenticationMode } from '../appstore/authenticateTypes';
|
||||
import { Target } from '../types';
|
||||
import { validateProvisioningProfileAsync } from '../validators/validateProvisioningProfile';
|
||||
|
||||
@@ -102,9 +107,18 @@ export class SetUpProvisioningProfile {
|
||||
if (areBuildCredentialsSetup) {
|
||||
return nullthrows(await getBuildCredentialsAsync(ctx, this.app, this.distributionType));
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Provisioning profile is not configured correctly. Run this command again in interactive mode.'
|
||||
if (ctx.freezeCredentials) {
|
||||
throw new ForbidCredentialModificationError(
|
||||
'Provisioning profile is not configured correctly. Remove the --freeze-credentials flag to configure it.'
|
||||
);
|
||||
} else if (
|
||||
ctx.nonInteractive &&
|
||||
ctx.appStore.defaultAuthenticationMode !== AuthenticationMode.API_KEY
|
||||
) {
|
||||
throw new InsufficientAuthenticationNonInteractiveError(
|
||||
`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${learnMore(
|
||||
'https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,11 +141,18 @@ export class SetUpProvisioningProfile {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: `${formatProvisioningProfileFromApple(
|
||||
currentProfileFromServer
|
||||
)} \n Would you like to reuse the original profile?`,
|
||||
});
|
||||
const isNonInteractiveOrUserDidConfirmAsync = async (): Promise<boolean> => {
|
||||
if (ctx.nonInteractive) {
|
||||
return true;
|
||||
}
|
||||
return await confirmAsync({
|
||||
message: `${formatProvisioningProfileFromApple(
|
||||
currentProfileFromServer
|
||||
)} \n Would you like to reuse the original profile?`,
|
||||
});
|
||||
};
|
||||
|
||||
const confirm = await isNonInteractiveOrUserDidConfirmAsync();
|
||||
if (!confirm) {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Teams } from '@expo/apple-utils';
|
||||
|
||||
import { AccountFragment, AppleTeamFragment, AppleTeamType } from '../../../graphql/generated';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppleTeamQuery } from '../api/graphql/queries/AppleTeamQuery';
|
||||
|
||||
/* *
|
||||
* Update the Apple Team for the app.
|
||||
* If the user is logged in, we sync the Team info from the Developer Portal
|
||||
* Else we ask the user to choose their Team Type (Company/Organization, Individual, In-House)
|
||||
*/
|
||||
export class UpdateAppleTeamInfo {
|
||||
constructor(
|
||||
private account: AccountFragment,
|
||||
private appleTeamIdentifier: string
|
||||
) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<AppleTeamFragment> {
|
||||
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
|
||||
ctx.graphqlClient,
|
||||
this.account.id,
|
||||
this.appleTeamIdentifier
|
||||
);
|
||||
const hasAppleTeamInfo = !!appleTeam && !!appleTeam.appleTeamType;
|
||||
if (hasAppleTeamInfo) {
|
||||
return appleTeam;
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
return await this.promptAndUpdateAppleTeamTypeAsync(ctx);
|
||||
}
|
||||
const teams = await Teams.getTeamsAsync();
|
||||
const team = teams.find(t => t.teamId === this.appleTeamIdentifier);
|
||||
if (!team) {
|
||||
return await this.promptAndUpdateAppleTeamTypeAsync(ctx);
|
||||
}
|
||||
const { teamId, type, name } = team;
|
||||
const teamType = appStoreTeamTypeToEnum(type);
|
||||
return await ctx.ios.createOrUpdateExistingAppleTeamAsync(ctx.graphqlClient, this.account, {
|
||||
appleTeamIdentifier: teamId,
|
||||
appleTeamName: name,
|
||||
appleTeamType: teamType,
|
||||
});
|
||||
}
|
||||
|
||||
private async promptAndUpdateAppleTeamTypeAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<AppleTeamFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
'Unable to get Apple Team information in non-interactive mode. Please re-run with interactive mode.'
|
||||
);
|
||||
}
|
||||
const teamType = await promptForAppleTeamTypeAsync();
|
||||
return await ctx.ios.createOrUpdateExistingAppleTeamAsync(ctx.graphqlClient, this.account, {
|
||||
appleTeamIdentifier: this.appleTeamIdentifier,
|
||||
appleTeamType: teamType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function appStoreTeamTypeToEnum(
|
||||
teamType: 'In-House' | 'Company/Organization' | 'Individual' | string
|
||||
): AppleTeamType | null {
|
||||
if (teamType === 'In-House') {
|
||||
return AppleTeamType.InHouse;
|
||||
} else if (teamType === 'Company/Organization') {
|
||||
return AppleTeamType.CompanyOrOrganization;
|
||||
} else if (teamType === 'Individual') {
|
||||
return AppleTeamType.Individual;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function promptForAppleTeamTypeAsync(): Promise<AppleTeamType> {
|
||||
const { appleTeamType } = await promptAsync({
|
||||
type: 'select',
|
||||
message: 'Select your Apple Team Type:',
|
||||
name: 'appleTeamType',
|
||||
choices: [
|
||||
{ title: 'Enterprise', value: AppleTeamType.InHouse },
|
||||
{ title: 'Company/Organization', value: AppleTeamType.CompanyOrOrganization },
|
||||
{ title: 'Individual', value: AppleTeamType.Individual },
|
||||
],
|
||||
});
|
||||
return appleTeamType;
|
||||
}
|
||||
@@ -1,21 +1,15 @@
|
||||
import { UserRole } from '@expo/apple-utils';
|
||||
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { testAscApiKey } from '../../../__tests__/fixtures-ios';
|
||||
import { getCredentialsFromUserAsync } from '../../../utils/promptForCredentials';
|
||||
import {
|
||||
AppStoreApiKeyPurpose,
|
||||
getAscApiKeyName,
|
||||
promptForAscApiKeyPathAsync,
|
||||
} from '../AscApiKeyUtils';
|
||||
import { getAscApiKeyName, promptForAscApiKeyPathAsync } from '../AscApiKeyUtils';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../utils/promptForCredentials');
|
||||
|
||||
function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
|
||||
return Object.keys(obj) as K[];
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
jest.mocked(promptAsync).mockClear();
|
||||
jest.mocked(getCredentialsFromUserAsync).mockClear();
|
||||
@@ -24,11 +18,8 @@ afterEach(() => {
|
||||
describe(getAscApiKeyName, () => {
|
||||
// Apple enforces a 30 char limit on this name
|
||||
it('produces a name under 30 chars', async () => {
|
||||
for (const value of enumKeys(AppStoreApiKeyPurpose)) {
|
||||
const purpose = AppStoreApiKeyPurpose[value];
|
||||
const name = getAscApiKeyName(purpose);
|
||||
expect(name.length < 30).toBe(true);
|
||||
}
|
||||
const name = getAscApiKeyName();
|
||||
expect(name.length < 30).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,7 +42,7 @@ describe(promptForAscApiKeyPathAsync, () => {
|
||||
authCtx: null,
|
||||
},
|
||||
});
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, [UserRole.ADMIN]);
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id',
|
||||
@@ -76,7 +67,7 @@ describe(promptForAscApiKeyPathAsync, () => {
|
||||
getAscApiKeyAsync: jest.fn(() => testAscApiKey),
|
||||
},
|
||||
});
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, [UserRole.ADMIN]);
|
||||
expect(ascApiKeyPath).toEqual({
|
||||
keyId: 'test-key-id',
|
||||
issuerId: 'test-issuer-id-from-apple',
|
||||
|
||||
+35
-5
@@ -11,7 +11,11 @@ import {
|
||||
testTarget,
|
||||
testTargets,
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../../errors';
|
||||
import { AuthenticationMode } from '../../appstore/authenticateTypes';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { ConfigureProvisioningProfile } from '../ConfigureProvisioningProfile';
|
||||
|
||||
@@ -24,15 +28,18 @@ describe('ConfigureProvisioningProfile', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
|
||||
});
|
||||
it('configures a Provisioning Profile in Interactive Mode', async () => {
|
||||
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
|
||||
test.each(testCases)('configures a Provisioning Profile in %s Mode', async mode => {
|
||||
const mockProvisioningProfileFromApple = {
|
||||
provisioningProfileId: testProvisioningProfileFragment.developerPortalIdentifier,
|
||||
provisioningProfile: testProvisioningProfileFragment.provisioningProfile,
|
||||
};
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
createProvisioningProfileAsync: jest.fn(() => testProvisioningProfile),
|
||||
@@ -57,7 +64,30 @@ describe('ConfigureProvisioningProfile', () => {
|
||||
// expect provisioning profile not to be updated on apple portal
|
||||
expect(jest.mocked(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
it('errors with --freeze-credentials flag', async () => {
|
||||
const ctx = createCtxMock({
|
||||
freezeCredentials: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const provProfConfigurator = new ConfigureProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
testDistCertFragmentNoDependencies,
|
||||
testProvisioningProfileFragment
|
||||
);
|
||||
await expect(provProfConfigurator.runAsync(ctx)).rejects.toThrowError(
|
||||
ForbidCredentialModificationError
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.updateProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be updated on apple portal
|
||||
expect(jest.mocked(ctx.appStore.useExistingProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors with wrong authentication type in nonInteractive mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
@@ -72,7 +102,7 @@ describe('ConfigureProvisioningProfile', () => {
|
||||
testProvisioningProfileFragment
|
||||
);
|
||||
await expect(provProfConfigurator.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
InsufficientAuthenticationNonInteractiveError
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be updated on expo servers
|
||||
|
||||
+35
-5
@@ -10,7 +10,11 @@ import {
|
||||
testTarget,
|
||||
testTargets,
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../../errors';
|
||||
import { AuthenticationMode } from '../../appstore/authenticateTypes';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { CreateProvisioningProfile } from '../CreateProvisioningProfile';
|
||||
|
||||
@@ -22,11 +26,15 @@ describe('CreateProvisioningProfile', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
|
||||
});
|
||||
it('creates a Provisioning Profile in Interactive Mode', async () => {
|
||||
|
||||
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
|
||||
test.each(testCases)('creates a Provisioning Profile in in %s Mode', async mode => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
createProvisioningProfileAsync: jest.fn(() => testProvisioningProfile),
|
||||
@@ -48,7 +56,29 @@ describe('CreateProvisioningProfile', () => {
|
||||
// expect provisioning profile to be created on apple portal
|
||||
expect(jest.mocked(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
it('errors with --freeze-credentials flag', async () => {
|
||||
const ctx = createCtxMock({
|
||||
freezeCredentials: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const createProvProfAction = new CreateProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
testDistCertFragmentNoDependencies
|
||||
);
|
||||
await expect(createProvProfAction.runAsync(ctx)).rejects.toThrowError(
|
||||
ForbidCredentialModificationError
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be created on expo servers
|
||||
expect(jest.mocked(ctx.ios.createProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
// expect provisioning profile not to be created on apple portal
|
||||
expect(jest.mocked(ctx.appStore.createProvisioningProfileAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors with wrong authentication type in nonInteractive mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
@@ -62,7 +92,7 @@ describe('CreateProvisioningProfile', () => {
|
||||
testDistCertFragmentNoDependencies
|
||||
);
|
||||
await expect(createProvProfAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
InsufficientAuthenticationNonInteractiveError
|
||||
);
|
||||
|
||||
// expect provisioning profile not to be created on expo servers
|
||||
|
||||
+186
-130
@@ -15,7 +15,11 @@ import {
|
||||
testTarget,
|
||||
testTargets,
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import {
|
||||
ForbidCredentialModificationError,
|
||||
InsufficientAuthenticationNonInteractiveError,
|
||||
} from '../../../errors';
|
||||
import { AuthenticationMode } from '../../appstore/authenticateTypes';
|
||||
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetUpProvisioningProfile } from '../SetUpProvisioningProfile';
|
||||
@@ -32,32 +36,185 @@ describe('SetUpProvisioningProfile', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse);
|
||||
});
|
||||
it('repairs existing Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
|
||||
const testCases = ['NON_INTERACTIVE', 'INTERACTIVE'];
|
||||
test.each(testCases)(
|
||||
'repairs existing Provisioning Profile with bad build credentials in %s Mode',
|
||||
async mode => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => [
|
||||
{
|
||||
provisioningProfileId: nullthrows(
|
||||
testCommonIosAppCredentialsFragment.iosAppBuildCredentialsList[0]
|
||||
.provisioningProfile
|
||||
).developerPortalIdentifier,
|
||||
},
|
||||
]),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppBuildCredentialsFragment
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
test.each(testCases)(
|
||||
'sets up a new Provisioning Profile with bad build credentials in %s Mode',
|
||||
async mode => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
}
|
||||
);
|
||||
|
||||
test.each(testCases)(
|
||||
'skips setting up a Provisioning Profile with prior build credentials configured properly in %s Mode',
|
||||
async mode => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => true);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
0
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
test.each(testCases)(
|
||||
'sets up a Provisioning Profile with no prior build credentials configured in %s Mode',
|
||||
async mode => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: mode === 'NON_INTERACTIVE',
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
defaultAuthenticationMode:
|
||||
mode === 'NON_INTERACTIVE' ? AuthenticationMode.API_KEY : AuthenticationMode.USER,
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
it('errors with --freeze-credentials flag', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => [
|
||||
{
|
||||
provisioningProfileId: nullthrows(
|
||||
testCommonIosAppCredentialsFragment.iosAppBuildCredentialsList[0].provisioningProfile
|
||||
).developerPortalIdentifier,
|
||||
},
|
||||
]),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppBuildCredentialsFragment
|
||||
),
|
||||
},
|
||||
freezeCredentials: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
@@ -68,79 +225,9 @@ describe('SetUpProvisioningProfile', () => {
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
await expect(setupProvisioningProfileAction.runAsync(ctx)).rejects.toThrowError(
|
||||
ForbidCredentialModificationError
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a new Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('skips setting up a Provisioning Profile with prior build credentials configured properly in Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => true);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
//listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
@@ -149,39 +236,8 @@ describe('SetUpProvisioningProfile', () => {
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a Provisioning Profile with no prior build credentials configured in Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
it('errors with wrong authentication type in nonInteractive mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
@@ -196,7 +252,7 @@ describe('SetUpProvisioningProfile', () => {
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await expect(setupProvisioningProfileAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
InsufficientAuthenticationNonInteractiveError
|
||||
);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
AppleDistributionCertificateFragment,
|
||||
ApplePushKeyFragment,
|
||||
AppleTeamFragment,
|
||||
AppleTeamInput,
|
||||
CommonIosAppCredentialsFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
@@ -189,28 +190,26 @@ export async function updateIosAppCredentialsAsync(
|
||||
{
|
||||
applePushKeyId,
|
||||
ascApiKeyIdForSubmissions,
|
||||
ascApiKeyIdForBuilds,
|
||||
}: {
|
||||
applePushKeyId?: string;
|
||||
ascApiKeyIdForSubmissions?: string;
|
||||
ascApiKeyIdForBuilds?: string;
|
||||
}
|
||||
): Promise<CommonIosAppCredentialsFragment> {
|
||||
let updatedAppCredentials = appCredentials;
|
||||
if (applePushKeyId) {
|
||||
updatedAppCredentials = await IosAppCredentialsMutation.setPushKeyAsync(
|
||||
graphqlClient,
|
||||
updatedAppCredentials.id,
|
||||
applePushKeyId
|
||||
);
|
||||
const updatedAppCredentials = appCredentials;
|
||||
if (!applePushKeyId && !ascApiKeyIdForSubmissions && !ascApiKeyIdForBuilds) {
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
if (ascApiKeyIdForSubmissions) {
|
||||
updatedAppCredentials =
|
||||
await IosAppCredentialsMutation.setAppStoreConnectApiKeyForSubmissionsAsync(
|
||||
graphqlClient,
|
||||
updatedAppCredentials.id,
|
||||
ascApiKeyIdForSubmissions
|
||||
);
|
||||
}
|
||||
return updatedAppCredentials;
|
||||
return await IosAppCredentialsMutation.updateIosAppCredentialsAsync(
|
||||
graphqlClient,
|
||||
appCredentials.id,
|
||||
{
|
||||
pushKeyId: applePushKeyId,
|
||||
appStoreConnectApiKeyForSubmissionsId: ascApiKeyIdForSubmissions,
|
||||
appStoreConnectApiKeyForBuildsId: ascApiKeyIdForBuilds,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function createOrGetExistingIosAppCredentialsWithBuildCredentialsAsync(
|
||||
@@ -263,7 +262,42 @@ async function createOrGetExistingIosAppCredentialsWithBuildCredentialsAsync(
|
||||
export async function createOrGetExistingAppleTeamAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
account: AccountFragment,
|
||||
{ appleTeamIdentifier, appleTeamName }: { appleTeamIdentifier: string; appleTeamName?: string }
|
||||
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput
|
||||
): Promise<AppleTeamFragment> {
|
||||
return await createOrManageExistingAppleTeamAsync(
|
||||
graphqlClient,
|
||||
account,
|
||||
{
|
||||
appleTeamIdentifier,
|
||||
appleTeamName,
|
||||
appleTeamType,
|
||||
},
|
||||
{ updateMissingFields: false }
|
||||
);
|
||||
}
|
||||
|
||||
export async function createOrUpdateExistingAppleTeamAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
account: AccountFragment,
|
||||
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput
|
||||
): Promise<AppleTeamFragment> {
|
||||
return await createOrManageExistingAppleTeamAsync(
|
||||
graphqlClient,
|
||||
account,
|
||||
{
|
||||
appleTeamIdentifier,
|
||||
appleTeamName,
|
||||
appleTeamType,
|
||||
},
|
||||
{ updateMissingFields: true }
|
||||
);
|
||||
}
|
||||
|
||||
async function createOrManageExistingAppleTeamAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
account: AccountFragment,
|
||||
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput,
|
||||
{ updateMissingFields }: { updateMissingFields: boolean }
|
||||
): Promise<AppleTeamFragment> {
|
||||
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
|
||||
graphqlClient,
|
||||
@@ -271,11 +305,24 @@ export async function createOrGetExistingAppleTeamAsync(
|
||||
appleTeamIdentifier
|
||||
);
|
||||
if (appleTeam) {
|
||||
const didTeamNameChange = appleTeamName && appleTeam.appleTeamName !== appleTeamName;
|
||||
const didTeamTypeChange = appleTeamType && appleTeam.appleTeamType !== appleTeamType;
|
||||
const didFieldsChange = didTeamNameChange || didTeamTypeChange;
|
||||
if (updateMissingFields && didFieldsChange) {
|
||||
return await AppleTeamMutation.updateAppleTeamAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appleTeamName: didTeamNameChange ? appleTeamName : undefined,
|
||||
appleTeamType: didTeamTypeChange ? appleTeamType : undefined,
|
||||
},
|
||||
appleTeam.id
|
||||
);
|
||||
}
|
||||
return appleTeam;
|
||||
} else {
|
||||
return await AppleTeamMutation.createAppleTeamAsync(
|
||||
graphqlClient,
|
||||
{ appleTeamIdentifier, appleTeamName },
|
||||
{ appleTeamIdentifier, appleTeamName, appleTeamType },
|
||||
account.id
|
||||
);
|
||||
}
|
||||
@@ -326,7 +373,7 @@ export async function getDevicesForAppleTeamAsync(
|
||||
): Promise<AppleDeviceFragment[]> {
|
||||
return await AppleDeviceQuery.getAllByAppleTeamIdentifierAsync(
|
||||
graphqlClient,
|
||||
account.id,
|
||||
account.name,
|
||||
appleTeamIdentifier,
|
||||
{
|
||||
useCache,
|
||||
@@ -521,6 +568,20 @@ export async function createAscApiKeyAsync(
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateAscApiKeyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
ascApiKey: AppStoreConnectApiKeyFragment,
|
||||
appleTeam: AppleTeamFragment
|
||||
): Promise<AppStoreConnectApiKeyFragment> {
|
||||
return await AppStoreConnectApiKeyMutation.updateAppStoreConnectApiKeyAsync(
|
||||
graphqlClient,
|
||||
{
|
||||
appleTeamId: appleTeam.id,
|
||||
},
|
||||
ascApiKey.id
|
||||
);
|
||||
}
|
||||
|
||||
export async function getAscApiKeysForAccountAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
account: AccountFragment
|
||||
|
||||
+40
@@ -7,8 +7,10 @@ import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
AppStoreConnectApiKeyFragment,
|
||||
AppStoreConnectApiKeyInput,
|
||||
AppStoreConnectApiKeyUpdateInput,
|
||||
CreateAppStoreConnectApiKeyMutation,
|
||||
DeleteAppStoreConnectApiKeyMutation,
|
||||
UpdateAppStoreConnectApiKeyMutation,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppStoreConnectApiKeyFragmentNode } from '../../../../../graphql/types/credentials/AppStoreConnectApiKey';
|
||||
|
||||
@@ -51,6 +53,44 @@ export const AppStoreConnectApiKeyMutation = {
|
||||
);
|
||||
return data.appStoreConnectApiKey.createAppStoreConnectApiKey;
|
||||
},
|
||||
async updateAppStoreConnectApiKeyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appStoreConnectApiKeyUpdateInput: AppStoreConnectApiKeyUpdateInput,
|
||||
appStoreConnectApiKeyId: string
|
||||
): Promise<AppStoreConnectApiKeyFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<UpdateAppStoreConnectApiKeyMutation>(
|
||||
gql`
|
||||
mutation UpdateAppStoreConnectApiKeyMutation(
|
||||
$appStoreConnectApiKeyUpdateInput: AppStoreConnectApiKeyUpdateInput!
|
||||
$id: ID!
|
||||
) {
|
||||
appStoreConnectApiKey {
|
||||
updateAppStoreConnectApiKey(
|
||||
appStoreConnectApiKeyUpdateInput: $appStoreConnectApiKeyUpdateInput
|
||||
id: $id
|
||||
) {
|
||||
id
|
||||
...AppStoreConnectApiKeyFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(AppStoreConnectApiKeyFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appStoreConnectApiKeyUpdateInput,
|
||||
appStoreConnectApiKeyId,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
assert(
|
||||
data.appStoreConnectApiKey.updateAppStoreConnectApiKey,
|
||||
'GraphQL: `updateAppStoreConnectApiKey` not defined in server response'
|
||||
);
|
||||
return data.appStoreConnectApiKey.updateAppStoreConnectApiKey;
|
||||
},
|
||||
async deleteAppStoreConnectApiKeyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appStoreConnectApiKeyId: string
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
AccountFragment,
|
||||
AppleTeamFragment,
|
||||
AppleTeamInput,
|
||||
AppleTeamUpdateInput,
|
||||
CreateAppleTeamMutation,
|
||||
UpdateAppleTeamMutation,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AccountFragmentNode } from '../../../../../graphql/types/Account';
|
||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||
@@ -50,4 +52,40 @@ export const AppleTeamMutation = {
|
||||
);
|
||||
return data.appleTeam.createAppleTeam;
|
||||
},
|
||||
async updateAppleTeamAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appleTeamUpdateInput: AppleTeamUpdateInput,
|
||||
appleTeamId: string
|
||||
): Promise<AppleTeamMutationResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<UpdateAppleTeamMutation>(
|
||||
gql`
|
||||
mutation UpdateAppleTeamMutation(
|
||||
$appleTeamUpdateInput: AppleTeamUpdateInput!
|
||||
$appleTeamId: ID!
|
||||
) {
|
||||
appleTeam {
|
||||
updateAppleTeam(appleTeamUpdateInput: $appleTeamUpdateInput, id: $appleTeamId) {
|
||||
id
|
||||
...AppleTeamFragment
|
||||
account {
|
||||
id
|
||||
...AccountFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(AppleTeamFragmentNode)}
|
||||
${print(AccountFragmentNode)}
|
||||
`,
|
||||
{
|
||||
appleTeamUpdateInput,
|
||||
appleTeamId,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.appleTeam.updateAppleTeam;
|
||||
},
|
||||
};
|
||||
+39
@@ -10,6 +10,7 @@ import {
|
||||
IosAppCredentialsInput,
|
||||
SetAppStoreConnectApiKeyForSubmissionsMutation,
|
||||
SetPushKeyMutation,
|
||||
UpdateIosAppCredentialsMutation,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { CommonIosAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppCredentials';
|
||||
|
||||
@@ -56,6 +57,44 @@ export const IosAppCredentialsMutation = {
|
||||
);
|
||||
return data.iosAppCredentials.createIosAppCredentials;
|
||||
},
|
||||
async updateIosAppCredentialsAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
iosAppCredentialsId: string,
|
||||
iosAppCredentialsInput: IosAppCredentialsInput
|
||||
): Promise<CommonIosAppCredentialsFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<UpdateIosAppCredentialsMutation>(
|
||||
gql`
|
||||
mutation UpdateIosAppCredentialsMutation(
|
||||
$iosAppCredentialsInput: IosAppCredentialsInput!
|
||||
$iosAppCredentialsId: ID!
|
||||
) {
|
||||
iosAppCredentials {
|
||||
updateIosAppCredentials(
|
||||
id: $iosAppCredentialsId
|
||||
iosAppCredentialsInput: $iosAppCredentialsInput
|
||||
) {
|
||||
id
|
||||
...CommonIosAppCredentialsFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(CommonIosAppCredentialsFragmentNode)}
|
||||
`,
|
||||
{
|
||||
iosAppCredentialsInput,
|
||||
iosAppCredentialsId,
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
assert(
|
||||
data.iosAppCredentials.updateIosAppCredentials,
|
||||
'GraphQL: `updateIosAppCredentials` not defined in server response'
|
||||
);
|
||||
return data.iosAppCredentials.updateIosAppCredentials;
|
||||
},
|
||||
async setPushKeyAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
iosAppCredentialsId: string,
|
||||
|
||||
+60
-7
@@ -4,27 +4,76 @@ import gql from 'graphql-tag';
|
||||
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
AppStoreConnectApiKeyByAccountQuery,
|
||||
AppStoreConnectApiKeyFragment,
|
||||
AppStoreConnectApiKeysPaginatedByAccountQuery,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppStoreConnectApiKeyFragmentNode } from '../../../../../graphql/types/credentials/AppStoreConnectApiKey';
|
||||
import { Connection, QueryParams, fetchEntireDatasetAsync } from '../../../../../utils/relay';
|
||||
|
||||
export const AppStoreConnectApiKeyQuery = {
|
||||
async getAllForAccountAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string
|
||||
): Promise<AppStoreConnectApiKeyFragment[]> {
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<AppStoreConnectApiKeyFragment>> => {
|
||||
return await AppStoreConnectApiKeyQuery.getAllForAccountPaginatedAsync(
|
||||
graphqlClient,
|
||||
accountName,
|
||||
relayArgs
|
||||
);
|
||||
};
|
||||
return await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
progressBarLabel: 'fetching ASC Keys...',
|
||||
});
|
||||
},
|
||||
async getAllForAccountPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
{
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
}: { after?: string; first?: number; before?: string; last?: number }
|
||||
): Promise<
|
||||
AppStoreConnectApiKeysPaginatedByAccountQuery['account']['byName']['appStoreConnectApiKeysPaginated']
|
||||
> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppStoreConnectApiKeyByAccountQuery>(
|
||||
.query<AppStoreConnectApiKeysPaginatedByAccountQuery>(
|
||||
gql`
|
||||
query AppStoreConnectApiKeyByAccountQuery($accountName: String!) {
|
||||
query AppStoreConnectApiKeysPaginatedByAccountQuery(
|
||||
$accountName: String!
|
||||
$after: String
|
||||
$first: Int
|
||||
$before: String
|
||||
$last: Int
|
||||
) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
appStoreConnectApiKeys {
|
||||
id
|
||||
...AppStoreConnectApiKeyFragment
|
||||
appStoreConnectApiKeysPaginated(
|
||||
after: $after
|
||||
first: $first
|
||||
before: $before
|
||||
last: $last
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...AppStoreConnectApiKeyFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +82,10 @@ export const AppStoreConnectApiKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppStoreConnectApiKey'],
|
||||
@@ -40,6 +93,6 @@ export const AppStoreConnectApiKeyQuery = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.appStoreConnectApiKeys;
|
||||
return data.account.byName.appStoreConnectApiKeysPaginated;
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import assert from 'assert';
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
@@ -7,15 +6,16 @@ import { DeviceNotFoundError } from '../../../../../devices/utils/errors';
|
||||
import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
AppleDevice,
|
||||
AppleDeviceFilterInput,
|
||||
AppleDeviceFragment,
|
||||
AppleDevicesByAppleTeamQuery,
|
||||
AppleDevicesByIdentifierQuery,
|
||||
AppleDevicesByTeamIdentifierQuery,
|
||||
AppleDevicesByTeamIdentifierQueryVariables,
|
||||
AppleDevicesPaginatedByAccountQuery,
|
||||
AppleTeamFragment,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleDeviceFragmentNode } from '../../../../../graphql/types/credentials/AppleDevice';
|
||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||
import { Connection, QueryParams, fetchEntireDatasetAsync } from '../../../../../utils/relay';
|
||||
import { formatAppleTeam } from '../../../actions/AppleTeamFormatting';
|
||||
|
||||
export type AppleDeviceFragmentWithAppleTeam = AppleDeviceFragment & {
|
||||
@@ -38,51 +38,25 @@ export type AppleDevicesByIdentifierQueryResult = AppleDeviceQueryResult & {
|
||||
export const AppleDeviceQuery = {
|
||||
async getAllByAppleTeamIdentifierAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountId: string,
|
||||
accountName: string,
|
||||
appleTeamIdentifier: string,
|
||||
{ useCache = true }: { useCache?: boolean } = {}
|
||||
): Promise<AppleDeviceFragmentWithAppleTeam[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppleDevicesByAppleTeamQuery>(
|
||||
gql`
|
||||
query AppleDevicesByAppleTeamQuery($accountId: ID!, $appleTeamIdentifier: String!) {
|
||||
appleTeam {
|
||||
byAppleTeamIdentifier(accountId: $accountId, identifier: $appleTeamIdentifier) {
|
||||
id
|
||||
...AppleTeamFragment
|
||||
appleDevices {
|
||||
id
|
||||
...AppleDeviceFragment
|
||||
appleTeam {
|
||||
id
|
||||
...AppleTeamFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(AppleTeamFragmentNode)}
|
||||
${print(AppleDeviceFragmentNode)}
|
||||
`,
|
||||
{
|
||||
accountId,
|
||||
appleTeamIdentifier,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppleDevice'],
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
assert(
|
||||
data.appleTeam.byAppleTeamIdentifier,
|
||||
'byAppleTeamIdentifier should be defined in this context - enforced by GraphQL'
|
||||
);
|
||||
const { appleDevices } = data.appleTeam.byAppleTeamIdentifier;
|
||||
assert(appleDevices, 'Apple Devices should be defined in this context - enforced by GraphQL');
|
||||
return appleDevices;
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<AppleDeviceFragmentWithAppleTeam>> => {
|
||||
return await AppleDeviceQuery.getAllForAccountPaginatedAsync(graphqlClient, accountName, {
|
||||
...relayArgs,
|
||||
filter: {
|
||||
appleTeamIdentifier,
|
||||
},
|
||||
useCache,
|
||||
});
|
||||
};
|
||||
return await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
progressBarLabel: 'Fetching Apple devices...',
|
||||
});
|
||||
},
|
||||
|
||||
async getAllForAppleTeamAsync(
|
||||
@@ -139,41 +113,21 @@ export const AppleDeviceQuery = {
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
identifier: string
|
||||
): Promise<AppleDevicesByIdentifierQueryResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppleDevicesByIdentifierQuery>(
|
||||
gql`
|
||||
query AppleDevicesByIdentifier($accountName: String!, $identifier: String!) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
appleDevices(identifier: $identifier) {
|
||||
id
|
||||
model
|
||||
identifier
|
||||
name
|
||||
deviceClass
|
||||
enabled
|
||||
appleTeam {
|
||||
id
|
||||
appleTeamIdentifier
|
||||
appleTeamName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ accountName, identifier },
|
||||
{
|
||||
additionalTypenames: ['AppleDevice', 'AppleTeam'],
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
const device = data.account.byName.appleDevices[0];
|
||||
): Promise<AppleDeviceFragmentWithAppleTeam> {
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<AppleDeviceFragmentWithAppleTeam>> => {
|
||||
return await AppleDeviceQuery.getAllForAccountPaginatedAsync(graphqlClient, accountName, {
|
||||
...relayArgs,
|
||||
filter: {
|
||||
identifier,
|
||||
},
|
||||
});
|
||||
};
|
||||
const devices = await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
});
|
||||
const device = devices[0];
|
||||
if (!device) {
|
||||
throw new DeviceNotFoundError(
|
||||
`Device with id ${identifier} was not found on account ${accountName}.`
|
||||
@@ -181,4 +135,86 @@ export const AppleDeviceQuery = {
|
||||
}
|
||||
return device;
|
||||
},
|
||||
async getAllForAccountPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
{
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
filter,
|
||||
useCache = true,
|
||||
}: {
|
||||
after?: string;
|
||||
first?: number;
|
||||
before?: string;
|
||||
last?: number;
|
||||
filter?: AppleDeviceFilterInput;
|
||||
useCache?: boolean;
|
||||
}
|
||||
): Promise<AppleDevicesPaginatedByAccountQuery['account']['byName']['appleDevicesPaginated']> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppleDevicesPaginatedByAccountQuery>(
|
||||
gql`
|
||||
query AppleDevicesPaginatedByAccountQuery(
|
||||
$accountName: String!
|
||||
$after: String
|
||||
$first: Int
|
||||
$before: String
|
||||
$last: Int
|
||||
$filter: AppleDeviceFilterInput
|
||||
) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
appleDevicesPaginated(
|
||||
after: $after
|
||||
first: $first
|
||||
before: $before
|
||||
last: $last
|
||||
filter: $filter
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...AppleDeviceFragment
|
||||
appleTeam {
|
||||
id
|
||||
...AppleTeamFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${print(AppleTeamFragmentNode)}
|
||||
${print(AppleDeviceFragmentNode)}
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
filter,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppleDevice'],
|
||||
requestPolicy: useCache ? 'cache-first' : 'network-only',
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.appleDevicesPaginated;
|
||||
},
|
||||
};
|
||||
+60
-7
@@ -5,13 +5,14 @@ import gql from 'graphql-tag';
|
||||
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import {
|
||||
AppleDistributionCertificateByAccountQuery,
|
||||
AppleDistributionCertificateByAppQuery,
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleDistributionCertificatesPaginatedByAccountQuery,
|
||||
IosDistributionType,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { AppleDistributionCertificateFragmentNode } from '../../../../../graphql/types/credentials/AppleDistributionCertificate';
|
||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||
import { Connection, QueryParams, fetchEntireDatasetAsync } from '../../../../../utils/relay';
|
||||
|
||||
export const AppleDistributionCertificateQuery = {
|
||||
async getForAppAsync(
|
||||
@@ -81,17 +82,65 @@ export const AppleDistributionCertificateQuery = {
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string
|
||||
): Promise<AppleDistributionCertificateFragment[]> {
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<AppleDistributionCertificateFragment>> => {
|
||||
return await AppleDistributionCertificateQuery.getAllForAccountPaginatedAsync(
|
||||
graphqlClient,
|
||||
accountName,
|
||||
relayArgs
|
||||
);
|
||||
};
|
||||
return await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
progressBarLabel: 'fetching Apple Distribution Certificates...',
|
||||
});
|
||||
},
|
||||
async getAllForAccountPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
{
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
}: { after?: string; first?: number; before?: string; last?: number }
|
||||
): Promise<
|
||||
AppleDistributionCertificatesPaginatedByAccountQuery['account']['byName']['appleDistributionCertificatesPaginated']
|
||||
> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppleDistributionCertificateByAccountQuery>(
|
||||
.query<AppleDistributionCertificatesPaginatedByAccountQuery>(
|
||||
gql`
|
||||
query AppleDistributionCertificateByAccountQuery($accountName: String!) {
|
||||
query AppleDistributionCertificatesPaginatedByAccountQuery(
|
||||
$accountName: String!
|
||||
$after: String
|
||||
$first: Int
|
||||
$before: String
|
||||
$last: Int
|
||||
) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
appleDistributionCertificates {
|
||||
id
|
||||
...AppleDistributionCertificateFragment
|
||||
appleDistributionCertificatesPaginated(
|
||||
after: $after
|
||||
first: $first
|
||||
before: $before
|
||||
last: $last
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...AppleDistributionCertificateFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +149,10 @@ export const AppleDistributionCertificateQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['AppleDistributionCertificate'],
|
||||
@@ -107,6 +160,6 @@ export const AppleDistributionCertificateQuery = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.appleDistributionCertificates;
|
||||
return data.account.byName.appleDistributionCertificatesPaginated;
|
||||
},
|
||||
};
|
||||
@@ -3,25 +3,75 @@ import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
||||
import { ApplePushKeyByAccountQuery, ApplePushKeyFragment } from '../../../../../graphql/generated';
|
||||
import {
|
||||
ApplePushKeyFragment,
|
||||
ApplePushKeysPaginatedByAccountQuery,
|
||||
} from '../../../../../graphql/generated';
|
||||
import { ApplePushKeyFragmentNode } from '../../../../../graphql/types/credentials/ApplePushKey';
|
||||
import { Connection, QueryParams, fetchEntireDatasetAsync } from '../../../../../utils/relay';
|
||||
|
||||
export const ApplePushKeyQuery = {
|
||||
async getAllForAccountAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string
|
||||
): Promise<ApplePushKeyFragment[]> {
|
||||
const paginatedGetterAsync = async (
|
||||
relayArgs: QueryParams
|
||||
): Promise<Connection<ApplePushKeyFragment>> => {
|
||||
return await ApplePushKeyQuery.getAllForAccountPaginatedAsync(
|
||||
graphqlClient,
|
||||
accountName,
|
||||
relayArgs
|
||||
);
|
||||
};
|
||||
return await fetchEntireDatasetAsync({
|
||||
paginatedGetterAsync,
|
||||
progressBarLabel: 'fetching Apple Push Keys...',
|
||||
});
|
||||
},
|
||||
async getAllForAccountPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
accountName: string,
|
||||
{
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
}: { after?: string; first?: number; before?: string; last?: number }
|
||||
): Promise<ApplePushKeysPaginatedByAccountQuery['account']['byName']['applePushKeysPaginated']> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ApplePushKeyByAccountQuery>(
|
||||
.query<ApplePushKeysPaginatedByAccountQuery>(
|
||||
gql`
|
||||
query ApplePushKeyByAccountQuery($accountName: String!) {
|
||||
query ApplePushKeysPaginatedByAccountQuery(
|
||||
$accountName: String!
|
||||
$after: String
|
||||
$first: Int
|
||||
$before: String
|
||||
$last: Int
|
||||
) {
|
||||
account {
|
||||
byName(accountName: $accountName) {
|
||||
id
|
||||
applePushKeys {
|
||||
id
|
||||
...ApplePushKeyFragment
|
||||
applePushKeysPaginated(
|
||||
after: $after
|
||||
first: $first
|
||||
before: $before
|
||||
last: $last
|
||||
) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...ApplePushKeyFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +80,10 @@ export const ApplePushKeyQuery = {
|
||||
`,
|
||||
{
|
||||
accountName,
|
||||
after,
|
||||
first,
|
||||
before,
|
||||
last,
|
||||
},
|
||||
{
|
||||
additionalTypenames: ['ApplePushKey'],
|
||||
@@ -37,6 +91,6 @@ export const ApplePushKeyQuery = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.account.byName.applePushKeys;
|
||||
return data.account.byName.applePushKeysPaginated;
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProfileType } from '@expo/app-store';
|
||||
import { ProfileType, UserRole } from '@expo/app-store';
|
||||
|
||||
import {
|
||||
AscApiKey,
|
||||
@@ -45,16 +45,23 @@ import { createOrReuseAdhocProvisioningProfileAsync } from './provisioningProfil
|
||||
import { createPushKeyAsync, listPushKeysAsync, revokePushKeyAsync } from './pushKey';
|
||||
import { hasAscEnvVars } from './resolveCredentials';
|
||||
import { Analytics } from '../../../analytics/AnalyticsManager';
|
||||
import { AppleTeamType } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { MinimalAscApiKey } from '../credentials';
|
||||
|
||||
export type AscApiKeyAuth = {
|
||||
ascApiKey: MinimalAscApiKey;
|
||||
appleTeam: { teamIdentifier: string; teamName?: string; teamType: AppleTeamType };
|
||||
};
|
||||
export default class AppStoreApi {
|
||||
public authCtx?: AuthCtx;
|
||||
public defaultAuthenticationMode: AuthenticationMode;
|
||||
public readonly ascApiKeyAuth?: AscApiKeyAuth;
|
||||
|
||||
constructor() {
|
||||
this.defaultAuthenticationMode = hasAscEnvVars()
|
||||
? AuthenticationMode.API_KEY
|
||||
: AuthenticationMode.USER;
|
||||
constructor(options: { ascApiKeyAuth?: AscApiKeyAuth } = {}) {
|
||||
this.ascApiKeyAuth = options.ascApiKeyAuth;
|
||||
this.defaultAuthenticationMode =
|
||||
hasAscEnvVars() || this.ascApiKeyAuth ? AuthenticationMode.API_KEY : AuthenticationMode.USER;
|
||||
}
|
||||
|
||||
public async ensureUserAuthenticatedAsync(options?: AuthenticateOptions): Promise<UserAuthCtx> {
|
||||
@@ -195,10 +202,10 @@ export default class AppStoreApi {
|
||||
|
||||
public async createAscApiKeyAsync(
|
||||
analytics: Analytics,
|
||||
{ nickname }: { nickname: string }
|
||||
{ nickname, roles }: { nickname: string; roles: UserRole[] }
|
||||
): Promise<AscApiKey> {
|
||||
const userCtx = await this.ensureUserAuthenticatedAsync();
|
||||
return await createAscApiKeyAsync(analytics, userCtx, { nickname });
|
||||
return await createAscApiKeyAsync(analytics, userCtx, { nickname, roles });
|
||||
}
|
||||
|
||||
public async revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
|
||||
|
||||
@@ -9,6 +9,7 @@ export enum AuthenticationMode {
|
||||
USER,
|
||||
}
|
||||
|
||||
// TODO: move this to graphql types
|
||||
export enum AppleTeamType {
|
||||
IN_HOUSE = 'IN_HOUSE',
|
||||
COMPANY_OR_ORGANIZATION = 'COMPANY_OR_ORGANIZATION',
|
||||
|
||||
+50
-50
@@ -1,54 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`print credentials prints the IosAppCredentials map 1`] = `
|
||||
"iOS Credentials
|
||||
Project @quinlanj/test52
|
||||
Bundle Identifier com.quinlanj.test52
|
||||
Apple Team test-apple-identifier
|
||||
|
||||
Push Key
|
||||
Developer Portal ID 9839M6AY8W
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
App Store Connect API Key
|
||||
Developer Portal ID NYN468XNN3
|
||||
Name Expo Submissions pl94k8_lnS
|
||||
Issuer ID 69a6de93-e4e4-47e3-e053-5b8c7c11a4d1
|
||||
Roles ADMIN
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
|
||||
App Store Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 6 months ago
|
||||
|
||||
Provisioning Profile
|
||||
Developer Portal ID test-profile-id
|
||||
Status active
|
||||
Expiration Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 5 months ago
|
||||
|
||||
|
||||
Ad Hoc Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 6 months ago
|
||||
|
||||
Provisioning Profile
|
||||
Developer Portal ID test-profile-id
|
||||
Status active
|
||||
Expiration Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 5 months ago
|
||||
"
|
||||
"iOS Credentials
|
||||
Project @quinlanj/test52
|
||||
Bundle Identifier com.quinlanj.test52
|
||||
Apple Team test-apple-identifier
|
||||
|
||||
Push Key
|
||||
Developer Portal ID 9839M6AY8W
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
EAS Submit App Store Connect API Key
|
||||
Developer Portal ID NYN468XNN3
|
||||
Name Expo Submissions pl94k8_lnS
|
||||
Issuer ID 69a6de93-e4e4-47e3-e053-5b8c7c11a4d1
|
||||
Roles ADMIN
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
|
||||
App Store Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 6 months ago
|
||||
|
||||
Provisioning Profile
|
||||
Developer Portal ID test-profile-id
|
||||
Status active
|
||||
Expiration Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 5 months ago
|
||||
|
||||
|
||||
Ad Hoc Configuration
|
||||
|
||||
Distribution Certificate
|
||||
Serial Number test-serial
|
||||
Expiration Date Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 6 months ago
|
||||
|
||||
Provisioning Profile
|
||||
Developer Portal ID test-profile-id
|
||||
Status active
|
||||
Expiration Wed, 13 Oct 2021 18:28:44 UTC
|
||||
Apple Team test-apple-identifier
|
||||
Updated 5 months ago
|
||||
"
|
||||
`;
|
||||
@@ -12,6 +12,7 @@ import { APPLE_DEVICE_CLASS_LABELS } from '../../../graphql/types/credentials/Ap
|
||||
import Log from '../../../log';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import formatFields from '../../../utils/formatFields';
|
||||
import { AppStoreApiKeyPurpose } from '../actions/AscApiKeyUtils';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { App, IosAppBuildCredentialsMap, IosAppCredentialsMap, Target } from '../types';
|
||||
|
||||
@@ -92,7 +93,12 @@ export function displayIosCredentials(
|
||||
continue;
|
||||
}
|
||||
|
||||
const { appleTeam, pushKey, appStoreConnectApiKeyForSubmissions } = targetAppCredentials;
|
||||
const {
|
||||
appleTeam,
|
||||
pushKey,
|
||||
appStoreConnectApiKeyForSubmissions,
|
||||
appStoreConnectApiKeyForBuilds,
|
||||
} = targetAppCredentials;
|
||||
if (appleTeam) {
|
||||
const { appleTeamIdentifier, appleTeamName } = appleTeam;
|
||||
fields.push({
|
||||
@@ -107,7 +113,15 @@ export function displayIosCredentials(
|
||||
}
|
||||
|
||||
if (appStoreConnectApiKeyForSubmissions) {
|
||||
displayAscApiKey(appStoreConnectApiKeyForSubmissions, fields);
|
||||
displayAscApiKey(
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE,
|
||||
appStoreConnectApiKeyForSubmissions,
|
||||
fields
|
||||
);
|
||||
}
|
||||
|
||||
if (appStoreConnectApiKeyForBuilds) {
|
||||
displayAscApiKey(AppStoreApiKeyPurpose.BUILD_SERVICE, appStoreConnectApiKeyForBuilds, fields);
|
||||
}
|
||||
|
||||
const sortedIosAppBuildCredentialsList = sortBuildCredentialsByDistributionType(
|
||||
@@ -236,10 +250,11 @@ function displayApplePushKey(
|
||||
}
|
||||
|
||||
function displayAscApiKey(
|
||||
purpose: AppStoreApiKeyPurpose,
|
||||
maybeAscApiKey: AppStoreConnectApiKeyFragment | null,
|
||||
fields: { label: string; value: string }[]
|
||||
): void {
|
||||
fields.push({ label: 'App Store Connect API Key', value: '' });
|
||||
fields.push({ label: `${purpose.toString()} App Store Connect API Key`, value: '' });
|
||||
if (maybeAscApiKey) {
|
||||
const { keyIdentifier, issuerIdentifier, appleTeam, name, roles, updatedAt } = maybeAscApiKey;
|
||||
fields.push({ label: 'Developer Portal ID', value: keyIdentifier });
|
||||
|
||||
@@ -29,7 +29,6 @@ export enum AndroidActionType {
|
||||
UseExistingGsaKeyForSubmissions,
|
||||
RemoveGsaKey,
|
||||
SetUpGsaKeyForSubmissions,
|
||||
CreateGsaKeyForFcmV1,
|
||||
UseExistingGsaKeyForFcmV1,
|
||||
RemoveGsaKeyForFcmV1,
|
||||
SetUpGsaKeyForFcmV1,
|
||||
@@ -56,8 +55,8 @@ export enum IosActionType {
|
||||
CreatePushKey,
|
||||
UseExistingPushKey,
|
||||
RemovePushKey,
|
||||
SetUpAscApiKeyForSubmissions,
|
||||
UseExistingAscApiKeyForSubmissions,
|
||||
CreateAscApiKeyForSubmissions,
|
||||
SetUpAscApiKeyForService,
|
||||
CreateAscApiKeyForService,
|
||||
UseExistingAscApiKeyForService,
|
||||
RemoveAscApiKey,
|
||||
}
|
||||
@@ -77,22 +77,22 @@ export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
export function getAscApiKeyActions(): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
value: IosActionType.SetUpAscApiKeyForSubmissions,
|
||||
title: 'Set up your project to use an API Key for EAS Submit',
|
||||
value: IosActionType.SetUpAscApiKeyForService,
|
||||
title: `Set up your project to use an API Key`,
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.UseExistingAscApiKeyForSubmissions,
|
||||
title: 'Use an existing API Key for EAS Submit',
|
||||
value: IosActionType.UseExistingAscApiKeyForService,
|
||||
title: `Use an existing API Key in your project`,
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.CreateAscApiKeyForSubmissions,
|
||||
title: 'Add a new API Key For EAS Submit',
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
value: IosActionType.CreateAscApiKeyForService,
|
||||
title: `Add a new API Key to use in your project`,
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.RemoveAscApiKey,
|
||||
|
||||
@@ -85,7 +85,7 @@ export class ManageIos {
|
||||
});
|
||||
const buildCredentialsActions = getBuildCredentialsActions(ctx);
|
||||
const pushKeyActions = getPushKeyActions(ctx);
|
||||
const ascApiKeyActions = getAscApiKeyActions(ctx);
|
||||
const ascApiKeyActions = getAscApiKeyActions();
|
||||
|
||||
await ctx.bestEffortAppStoreAuthenticateAsync();
|
||||
|
||||
@@ -226,8 +226,6 @@ export class ManageIos {
|
||||
await new CreateDistributionCertificate(account).runAsync(ctx);
|
||||
} else if (action === IosActionType.CreatePushKey) {
|
||||
await new CreatePushKey(account).runAsync(ctx);
|
||||
} else if (action === IosActionType.CreateAscApiKeyForSubmissions) {
|
||||
await new CreateAscApiKey(account).runAsync(ctx, AppStoreApiKeyPurpose.SUBMISSION_SERVICE);
|
||||
} else if (action === IosActionType.RemovePushKey) {
|
||||
await new SelectAndRemovePushKey(account).runAsync(ctx);
|
||||
} else if (action === IosActionType.RemoveAscApiKey) {
|
||||
@@ -354,40 +352,29 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IosActionType.SetUpAscApiKeyForSubmissions: {
|
||||
await new SetUpAscApiKey(
|
||||
appLookupParams,
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
).runAsync(ctx);
|
||||
case IosActionType.SetUpAscApiKeyForService: {
|
||||
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||
await new SetUpAscApiKey(appLookupParams, purpose).runAsync(ctx);
|
||||
return;
|
||||
}
|
||||
case IosActionType.UseExistingAscApiKeyForSubmissions: {
|
||||
case IosActionType.UseExistingAscApiKeyForService: {
|
||||
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||
const ascApiKey = await selectAscApiKeysFromAccountAsync(ctx, appLookupParams.account, {
|
||||
filterDifferentAppleTeam: true,
|
||||
});
|
||||
if (ascApiKey) {
|
||||
await new AssignAscApiKey(appLookupParams).runAsync(
|
||||
ctx,
|
||||
ascApiKey,
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
);
|
||||
await new AssignAscApiKey(appLookupParams).runAsync(ctx, ascApiKey, purpose);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IosActionType.CreateAscApiKeyForSubmissions: {
|
||||
const ascApiKey = await new CreateAscApiKey(appLookupParams.account).runAsync(
|
||||
ctx,
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
);
|
||||
case IosActionType.CreateAscApiKeyForService: {
|
||||
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||
const ascApiKey = await new CreateAscApiKey(appLookupParams.account).runAsync(ctx, purpose);
|
||||
const confirm = await confirmAsync({
|
||||
message: `Do you want ${appLookupParams.projectName} to use the new API Key?`,
|
||||
});
|
||||
if (confirm) {
|
||||
await new AssignAscApiKey(appLookupParams).runAsync(
|
||||
ctx,
|
||||
ascApiKey,
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
);
|
||||
await new AssignAscApiKey(appLookupParams).runAsync(ctx, ascApiKey, purpose);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -431,4 +418,17 @@ export class ManageIos {
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
protected async selectAscApiKeyPurposeAsync(): Promise<AppStoreApiKeyPurpose> {
|
||||
const { keyPurpose } = await promptAsync({
|
||||
type: 'select',
|
||||
message: `Select the service you'd like to use your ASC Api Key for:`,
|
||||
name: 'keyPurpose',
|
||||
choices: [
|
||||
{ title: 'EAS Submit', value: AppStoreApiKeyPurpose.SUBMISSION_SERVICE },
|
||||
{ title: 'EAS Build', value: AppStoreApiKeyPurpose.BUILD_SERVICE },
|
||||
],
|
||||
});
|
||||
return keyPurpose;
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,18 @@ export class SetUpBuildCredentialsCommandAction {
|
||||
public readonly projectInfo: CredentialsContextProjectInfo | null,
|
||||
public readonly getDynamicPrivateProjectConfigAsync: DynamicConfigContextFn,
|
||||
private readonly platform: Platform,
|
||||
private readonly profileName: string
|
||||
private readonly profileName: string,
|
||||
private readonly projectDir: string
|
||||
) {}
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
if (this.platform === Platform.IOS) {
|
||||
return await new SetUpIosBuildCredentials(this, process.cwd(), this.profileName).runAsync();
|
||||
return await new SetUpIosBuildCredentials(this, this.projectDir, this.profileName).runAsync();
|
||||
}
|
||||
return await new SetUpAndroidBuildCredentials(this, process.cwd(), this.profileName).runAsync();
|
||||
return await new SetUpAndroidBuildCredentials(
|
||||
this,
|
||||
this.projectDir,
|
||||
this.profileName
|
||||
).runAsync();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export class SetUpIosBuildCredentials extends ManageIos {
|
||||
}
|
||||
|
||||
const ctx = new CredentialsContext({
|
||||
projectDir: process.cwd(),
|
||||
projectDir: this.projectDir,
|
||||
projectInfo,
|
||||
user: this.callingAction.actor,
|
||||
graphqlClient: this.callingAction.graphqlClient,
|
||||
|
||||
@@ -44,6 +44,7 @@ describe(AccountResolver, () => {
|
||||
],
|
||||
isExpoAdmin: false,
|
||||
featureGates: {},
|
||||
preferences: {},
|
||||
};
|
||||
|
||||
describe('when inside project dir', () => {
|
||||
@@ -51,6 +52,7 @@ describe(AccountResolver, () => {
|
||||
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||
id: 'test-project-id',
|
||||
fullName: '@foo/wat',
|
||||
name: 'wat',
|
||||
slug: 'wat',
|
||||
ownerAccount: {
|
||||
id: 'account_id_888',
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class DeviceCreateAction {
|
||||
await runDeveloperPortalMethodAsync(
|
||||
this.graphqlClient,
|
||||
this.appStoreApi,
|
||||
this.account.id,
|
||||
this.account,
|
||||
this.appleTeam
|
||||
);
|
||||
} else if (method === RegistrationMethod.INPUT) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import AppStoreApi from '../../../credentials/ios/appstore/AppStoreApi';
|
||||
import { getRequestContext } from '../../../credentials/ios/appstore/authenticate';
|
||||
import { AuthCtx } from '../../../credentials/ios/appstore/authenticateTypes';
|
||||
import { AppleDeviceClass, AppleTeam } from '../../../graphql/generated';
|
||||
import { AccountFragment, AppleDeviceClass, AppleTeam } from '../../../graphql/generated';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
@@ -25,14 +25,14 @@ const DEVICE_CLASS_TO_GRAPHQL_TYPE: Partial<Record<DeviceClass, AppleDeviceClass
|
||||
export async function runDeveloperPortalMethodAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appStoreApi: AppStoreApi,
|
||||
accountId: string,
|
||||
account: AccountFragment,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
|
||||
): Promise<void> {
|
||||
const appleAuthCtx = await appStoreApi.ensureAuthenticatedAsync();
|
||||
const unregisteredPortalDevices = await findUnregisteredPortalDevicesAsync(
|
||||
graphqlClient,
|
||||
appleAuthCtx,
|
||||
accountId,
|
||||
account.name,
|
||||
appleTeam
|
||||
);
|
||||
if (unregisteredPortalDevices.length === 0) {
|
||||
@@ -40,7 +40,7 @@ export async function runDeveloperPortalMethodAsync(
|
||||
return;
|
||||
}
|
||||
const devicesToImport = await chooseDevicesToImportAsync(unregisteredPortalDevices);
|
||||
await importDevicesAsync(graphqlClient, accountId, appleTeam, devicesToImport);
|
||||
await importDevicesAsync(graphqlClient, account.id, appleTeam, devicesToImport);
|
||||
}
|
||||
|
||||
async function importDevicesAsync(
|
||||
@@ -88,12 +88,12 @@ async function importDeviceChunkAsync(
|
||||
async function findUnregisteredPortalDevicesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
appleAuthCtx: AuthCtx,
|
||||
accountId: string,
|
||||
accountName: string,
|
||||
appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName' | 'id'>
|
||||
): Promise<Device[]> {
|
||||
const expoRegisteredDevices = await AppleDeviceQuery.getAllByAppleTeamIdentifierAsync(
|
||||
graphqlClient,
|
||||
accountId,
|
||||
accountName,
|
||||
appleTeam.appleTeamIdentifier,
|
||||
{ useCache: false }
|
||||
);
|
||||
|
||||
@@ -6,5 +6,6 @@ export function createCtxMock(options: { nonInteractive?: boolean } = {}): EASUp
|
||||
graphqlClient: jest.fn() as any,
|
||||
nonInteractive: options.nonInteractive ?? false,
|
||||
app: { exp: testAppJson, projectId: 'test-project-id', projectDir: '/' },
|
||||
vcsClient: jest.fn() as any,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ExpoConfig } from '@expo/config-types';
|
||||
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
export type EASUpdateContext = {
|
||||
graphqlClient: ExpoGraphqlClient;
|
||||
nonInteractive: boolean;
|
||||
app: { exp: ExpoConfig; projectId: string; projectDir: string };
|
||||
vcsClient: Client;
|
||||
};
|
||||
|
||||
export interface EASUpdateAction<T = any> {
|
||||
|
||||
+440
-247
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,8 @@ import {
|
||||
} from '../generated';
|
||||
import { UpdateFragmentNode } from '../types/Update';
|
||||
|
||||
const turtleJobRunId = process.env.EAS_BUILD_ID;
|
||||
|
||||
export const PublishMutation = {
|
||||
async getUploadURLsAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
@@ -57,7 +59,12 @@ export const PublishMutation = {
|
||||
}
|
||||
${print(UpdateFragmentNode)}
|
||||
`,
|
||||
{ publishUpdateGroupsInput }
|
||||
{
|
||||
publishUpdateGroupsInput: publishUpdateGroupsInput.map(input => ({
|
||||
...input,
|
||||
turtleJobRunId,
|
||||
})),
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import assert from 'assert';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
MarkCliDoneInOnboardingUserPreferencesMutation,
|
||||
MarkCliDoneInOnboardingUserPreferencesMutationVariables,
|
||||
UserPreferencesOnboardingInput,
|
||||
} from '../generated';
|
||||
|
||||
export const UserPreferencesMutation = {
|
||||
async markCliDoneInOnboardingUserPreferencesAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
userPreferencesData: Partial<UserPreferencesOnboardingInput> & { appId: string }
|
||||
): Promise<{
|
||||
isCLIDone: boolean;
|
||||
appId: string;
|
||||
}> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<
|
||||
MarkCliDoneInOnboardingUserPreferencesMutation,
|
||||
MarkCliDoneInOnboardingUserPreferencesMutationVariables
|
||||
>(
|
||||
gql`
|
||||
mutation MarkCliDoneInOnboardingUserPreferencesMutation(
|
||||
$preferences: UserPreferencesInput!
|
||||
) {
|
||||
me {
|
||||
setPreferences(preferences: $preferences) {
|
||||
onboarding {
|
||||
appId
|
||||
isCLIDone
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
preferences: {
|
||||
onboarding: {
|
||||
...userPreferencesData,
|
||||
isCLIDone: true,
|
||||
lastUsed: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const appId = data.me.setPreferences.onboarding?.appId;
|
||||
assert(appId, 'App ID must be defined');
|
||||
const isCLIDone = data.me.setPreferences.onboarding?.isCLIDone;
|
||||
assert(isCLIDone, 'isCLIDone must be defined and true');
|
||||
return {
|
||||
appId,
|
||||
isCLIDone,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -22,6 +22,16 @@ export const UserQuery = {
|
||||
id
|
||||
...AccountFragment
|
||||
}
|
||||
preferences {
|
||||
onboarding {
|
||||
appId
|
||||
platform
|
||||
deviceType
|
||||
environment
|
||||
isCLIDone
|
||||
lastUsed
|
||||
}
|
||||
}
|
||||
}
|
||||
... on Robot {
|
||||
firstName
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AccountFragmentNode } from './Account';
|
||||
export const AppFragmentNode = gql`
|
||||
fragment AppFragment on App {
|
||||
id
|
||||
name
|
||||
fullName
|
||||
slug
|
||||
ownerAccount {
|
||||
@@ -12,6 +13,13 @@ export const AppFragmentNode = gql`
|
||||
name
|
||||
...AccountFragment
|
||||
}
|
||||
githubRepository {
|
||||
id
|
||||
metadata {
|
||||
githubRepoOwnerName
|
||||
githubRepoName
|
||||
}
|
||||
}
|
||||
}
|
||||
${AccountFragmentNode}
|
||||
`;
|
||||
@@ -37,7 +37,6 @@ export const BuildFragmentNode = gql`
|
||||
}
|
||||
}
|
||||
channel
|
||||
releaseChannel
|
||||
distribution
|
||||
iosEnterpriseProvisioning
|
||||
buildProfile
|
||||
|
||||
@@ -12,6 +12,7 @@ export const AppStoreConnectApiKeyFragmentNode = gql`
|
||||
}
|
||||
issuerIdentifier
|
||||
keyIdentifier
|
||||
keyP8
|
||||
name
|
||||
roles
|
||||
createdAt
|
||||
|
||||
@@ -5,5 +5,6 @@ export const AppleTeamFragmentNode = gql`
|
||||
id
|
||||
appleTeamIdentifier
|
||||
appleTeamName
|
||||
appleTeamType
|
||||
}
|
||||
`;
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user