mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
864658df80 | ||
|
|
4510e86b64 | ||
|
|
4af4490abc | ||
|
|
37c4040203 | ||
|
|
55f9be3b5c | ||
|
|
152b25b93c | ||
|
|
ea0d0bb4dc | ||
|
|
37e360aead | ||
|
|
20211e6265 | ||
|
|
226c888cc9 | ||
|
|
0e9f8ec272 | ||
|
|
8eb5d8028d | ||
|
|
2fb296d116 | ||
|
|
010c1ee439 | ||
|
|
dddc4a0e95 | ||
|
|
679ccf19d9 | ||
|
|
5ba3cc6623 | ||
|
|
c1a4731762 | ||
|
|
767577e386 | ||
|
|
d2f620dbc6 | ||
|
|
0aa556c29e | ||
|
|
10cf003758 | ||
|
|
0e6254453a | ||
|
|
587a29892f | ||
|
|
db8245d954 | ||
|
|
0088306bca | ||
|
|
b6fc718713 |
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
@@ -3,7 +3,6 @@
|
||||
# Checklist
|
||||
|
||||
- [ ] I've added an entry to [CHANGELOG.md](https://github.com/expo/eas-cli/blob/main/CHANGELOG.md#main) if necessary. You can comment this pull request with `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]` and CHANGELOG.md will be updated automatically.
|
||||
- [ ] I've tagged the changelog entry with `[EAS BUILD API]` if it's a part of a breaking change to EAS Build API (only possible when updating `@expo/eas-build-job` package).
|
||||
|
||||
# Why
|
||||
|
||||
|
||||
+56
-20
@@ -8,20 +8,56 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add ASC Api Key generation workflow. ([#718](https://github.com/expo/eas-cli/pull/718) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add support for removal of App Store Connect Api Keys. ([#721](https://github.com/expo/eas-cli/pull/721) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow users to assign an ASC Api Key to their project. ([#719](https://github.com/expo/eas-cli/pull/719) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add setup support for ASC Api Keys. ([#733](https://github.com/expo/eas-cli/pull/733) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show initiating user display name when selecting a build to submit. ([#730](https://github.com/expo/eas-cli/pull/730) by [@barthap](https://github.com/barthap))
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix `dateformat` import issue when building with `--local` flag. ([a520d](https://github.com/expo/eas-cli/commit/a520d1750736d61d6489da6487879f35ed3deb23) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.36.0](https://github.com/expo/eas-cli/releases/tag/v0.36.0) - 2021-11-15
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Do not require manual `eas-cli-local-build-plugin` installation. An existing global installation (either with `yarn global add` or `npm install -g`) will not be used anymore. ([#753](https://github.com/expo/eas-cli/pull/753) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Use Keystore Service when `keytool` is not installed. ([#754](https://github.com/expo/eas-cli/pull/754) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix auto-submitting with `--auto-submit-with-profile`. ([#748](https://github.com/expo/eas-cli/pull/748) by [@dsokal](https://github.com/dsokal))
|
||||
- Pass env from build profile when resolving entitlements. ([#751](https://github.com/expo/eas-cli/pull/751) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Only prompt for Apple Id username if authenticating with an App Specific Password. ([#745](https://github.com/expo/eas-cli/pull/745) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Use same URL paths for local development of EAS CLI as production. ([#750](https://github.com/expo/eas-cli/pull/750) by [@ide](https://github.com/ide))
|
||||
- Grammar: Api -> API. ([#755](https://github.com/expo/eas-cli/pull/755) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add analytics for EAS Submit. ([#752](https://github.com/expo/eas-cli/pull/752) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add an 'attempt' event to build and submit analytics. ([#757](https://github.com/expo/eas-cli/pull/757) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.35.0](https://github.com/expo/eas-cli/releases/tag/v0.35.0) - 2021-11-08
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add ASC API Key generation workflow. ([#718](https://github.com/expo/eas-cli/pull/718) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add support for removal of ASC API Keys. ([#721](https://github.com/expo/eas-cli/pull/721) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Allow users to assign an ASC API Key to their project. ([#719](https://github.com/expo/eas-cli/pull/719) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add setup support for ASC API Keys. ([#733](https://github.com/expo/eas-cli/pull/733) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show initiating user display name when selecting a build to submit. ([#730](https://github.com/expo/eas-cli/pull/730) by [@barthap](https://github.com/barthap))
|
||||
- Handle Apple servers maintenance error in `eas submit`. ([#738](https://github.com/expo/eas-cli/pull/738) by [@barthap](https://github.com/barthap))
|
||||
- Integrate ASC API Key with submissions workflow. ([#737](https://github.com/expo/eas-cli/pull/737) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Change EAS API server domain. ([#744](https://github.com/expo/eas-cli/pull/744) by [@ide](https://github.com/ide))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Clean up credentials prompt method. ([#728](https://github.com/expo/eas-cli/pull/728) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Bump @expo/apple-utils to 0.0.0-alpha.26. ([#723](https://github.com/expo/eas-cli/pull/723] by [@brentvatne](https://github.com/brentvatne))
|
||||
- Bump @expo/apple-utils to 0.0.0-alpha.26. ([#723](https://github.com/expo/eas-cli/pull/723) by [@brentvatne](https://github.com/brentvatne))
|
||||
- Grammar: replace setup with set up. ([#735](https://github.com/expo/eas-cli/pull/735) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Improve VCS workflow migration experience. ([#732](https://github.com/expo/eas-cli/pull/732) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.34.1](https://github.com/expo/eas-cli/releases/tag/0.34.1) - 2021-11-02
|
||||
## [0.34.1](https://github.com/expo/eas-cli/releases/tag/v0.34.1) - 2021-11-02
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
@@ -29,16 +65,16 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect Api Key graphql type and print support. ([#717](https://github.com/expo/eas-cli/pull/717) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add App Store Connect API Key graphql type and print support. ([#717](https://github.com/expo/eas-cli/pull/717) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.34.0](https://github.com/expo/eas-cli/releases/tag/0.34.0) - 2021-11-01
|
||||
## [0.34.0](https://github.com/expo/eas-cli/releases/tag/v0.34.0) - 2021-11-01
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- `[EAS BUILD API]` Use new build job format. ([#701](https://github.com/expo/eas-cli/pull/701) and [#711](https://github.com/expo/eas-cli/pull/711) by [@dsokal](https://github.com/dsokal))
|
||||
- Use new build job format. ([#701](https://github.com/expo/eas-cli/pull/701) and [#711](https://github.com/expo/eas-cli/pull/711) by [@dsokal](https://github.com/dsokal))
|
||||
- Don't print logs to `stderr`. ([#708](https://github.com/expo/eas-cli/pull/708) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove automatic migration for legacy `eas.json` format introduced in [v0.22.2](https://github.com/expo/eas-cli/releases/tag/v0.22.2). ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Add required `cli` field in root of `eas.json` ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Implement no-commit build workflow. Add required `cli` field in root of `eas.json`. ([#695](https://github.com/expo/eas-cli/pull/695) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
@@ -53,7 +89,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Remove unneeded parameters. ([#710](https://github.com/expo/eas-cli/pull/710) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enforce additionalTypenames for graphql queries. ([#709](https://github.com/expo/eas-cli/pull/709) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.33.1](https://github.com/expo/eas-cli/releases/tag/0.33.1) - 2021-10-22
|
||||
## [0.33.1](https://github.com/expo/eas-cli/releases/tag/v0.33.1) - 2021-10-22
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
@@ -63,7 +99,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- [eas-cli] Skip validating updates scripts integration, which no longer exist in SDK 43+ ([#706](https://github.com/expo/eas-cli/pull/706) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.33.0](https://github.com/expo/eas-cli/releases/tag/0.33.0) - 2021-10-20
|
||||
## [0.33.0](https://github.com/expo/eas-cli/releases/tag/v0.33.0) - 2021-10-20
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
@@ -86,11 +122,11 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Skip the second prompt for Apple ID if the user is already signed in with Apple. ([#691](https://github.com/expo/eas-cli/pull/691) by [@dsokal](https://github.com/dsokal))
|
||||
- Unify reading `app.json`. ([#692](https://github.com/expo/eas-cli/pull/692) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/0.32.0) - 2021-10-15
|
||||
## [0.32.0](https://github.com/expo/eas-cli/releases/tag/v0.32.0) - 2021-10-15
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add App Store Connect Api Key support to iOS submissions. ([#678](https://github.com/expo/eas-cli/pull/678) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add App Store Connect API Key support to iOS submissions. ([#678](https://github.com/expo/eas-cli/pull/678) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Create/list/revoke App Store Connect Api keys via Apple apis. ([#687](https://github.com/expo/eas-cli/pull/687) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add ability to select a build from a list in `eas submit` interactive mode. ([#688](https://github.com/expo/eas-cli/pull/688) by [@barthap](https://github.com/barthap))
|
||||
|
||||
@@ -100,16 +136,16 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
- Add App Store Connect Api Key fields to `eas.json`. ([#684](https://github.com/expo/eas-cli/pull/684) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add App Store Connect API Key fields to `eas.json`. ([#684](https://github.com/expo/eas-cli/pull/684) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Enable no-underscore-dangle eslint rule. ([#686](https://github.com/expo/eas-cli/pull/686) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.31.1](https://github.com/expo/eas-cli/releases/tag/0.31.1) - 2021-10-08
|
||||
## [0.31.1](https://github.com/expo/eas-cli/releases/tag/v0.31.1) - 2021-10-08
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Google Service Account Keys: Fix non-interactive bug in credentials service workflow ([#682](https://github.com/expo/eas-cli/pull/682) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.31.0](https://github.com/expo/eas-cli/releases/tag/0.31.0) - 2021-10-08
|
||||
## [0.31.0](https://github.com/expo/eas-cli/releases/tag/v0.31.0) - 2021-10-08
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
@@ -120,7 +156,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Integrate credentials service with Android submissions. ([#664](https://github.com/expo/eas-cli/pull/664) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Add option to review ad-hoc devices when reusing provisioning profile. ([#673](https://github.com/expo/eas-cli/pull/673) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/0.30.1) - 2021-10-06
|
||||
## [0.30.1](https://github.com/expo/eas-cli/releases/tag/v0.30.1) - 2021-10-06
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
@@ -499,7 +535,7 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- `[EAS BUILD API]` Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- Remove "Auto" option for `schemeBuildConfiguration` and make the old "Auto" behaviour the default. ([#394](https://github.com/expo/eas-cli/pull/394) by [@randomhajile](https://github.com/randomhajile))
|
||||
- Remove `experimental.disableIosBundleIdentifierValidation` flag from eas.json. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Deprecate `android.package` and `ios.bundleIdentifier` in app config for generic projects. EAS CLI depends on the values in native code now. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
- Remove application id synchronization (`android.package` and `ios.bundleIdentifier`) between app.json and native code when running builds. ([#407](https://github.com/expo/eas-cli/pull/407) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
+1
-7
@@ -1,10 +1,4 @@
|
||||
## Releasing a new version
|
||||
|
||||
1. Run `yarn release` in the repository root folder.
|
||||
2. Wait, the `release` workflow on GitHub Actions is going to take care of the rest (including publishing packages to npm and updating the changelog).
|
||||
3. Check the changelog. If any of the entries have been tagged with `[EAS BUILD API]`
|
||||
- Open `package.json` and note the version of `@expo/eas-build-job` used in the eas-cli.
|
||||
- Find the `Publish` commit in [expo/eas-build](https://github.com/expo/eas-build/commits) for that version. In most cases, it will be the latest `Publish` commit.
|
||||
- Find the `eas-cli-local-build-plugin` version published in that commit.
|
||||

|
||||
- Run `npm dist-tags add eas-cli-local-build-plugin@VERSION latest` with the version from the previous step.
|
||||
2. That's it! GitHub Actions is going to take care of the rest. Watch the #eas-cli Slack channel for a successful release notification.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.34.1"
|
||||
"version": "0.36.1"
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"watch": "yarn start",
|
||||
"eas": "packages/eas-cli/bin/run",
|
||||
"lint": "eslint . --ext .ts --ext .js",
|
||||
"release": "lerna version",
|
||||
"release": "./scripts/bin/run update-local-plugin && lerna version",
|
||||
"test": "jest",
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
},
|
||||
|
||||
+27
-28
@@ -73,7 +73,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/account/login.js)_
|
||||
_See code: [src/commands/account/login.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/account/login.js)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -87,7 +87,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/account/logout.js)_
|
||||
_See code: [src/commands/account/logout.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/account/logout.js)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -101,7 +101,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/account/view.js)_
|
||||
_See code: [src/commands/account/view.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/account/view.js)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -112,7 +112,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/analytics.js)_
|
||||
_See code: [src/commands/analytics.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/analytics.js)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -146,7 +146,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/build/index.js)_
|
||||
_See code: [src/commands/build/index.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/build/index.js)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -157,7 +157,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
_See code: [src/commands/build/cancel.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/build/cancel.js)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -171,7 +171,7 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/build/configure.js)_
|
||||
_See code: [src/commands/build/configure.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/build/configure.js)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -204,7 +204,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/build/list.js)_
|
||||
_See code: [src/commands/build/list.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/build/list.js)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -218,7 +218,7 @@ OPTIONS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/build/view.js)_
|
||||
_See code: [src/commands/build/view.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/build/view.js)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -233,7 +233,7 @@ OPTIONS
|
||||
--profile=profile
|
||||
```
|
||||
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/config.js)_
|
||||
_See code: [src/commands/config.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/config.js)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -244,7 +244,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/credentials.js)_
|
||||
_See code: [src/commands/credentials.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/credentials.js)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -255,7 +255,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/device/create.js)_
|
||||
_See code: [src/commands/device/create.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/device/create.js)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -269,7 +269,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/device/list.js)_
|
||||
_See code: [src/commands/device/list.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/device/list.js)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -280,7 +280,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/device/view.js)_
|
||||
_See code: [src/commands/device/view.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/device/view.js)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -291,7 +291,7 @@ USAGE
|
||||
$ eas diagnostics
|
||||
```
|
||||
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
_See code: [src/commands/diagnostics.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/diagnostics.js)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -319,7 +319,7 @@ USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/project/info.js)_
|
||||
_See code: [src/commands/project/info.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/project/info.js)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -333,7 +333,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/project/init.js)_
|
||||
_See code: [src/commands/project/init.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/project/init.js)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
@@ -350,7 +350,7 @@ OPTIONS
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/secret/create.js)_
|
||||
_See code: [src/commands/secret/create.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/secret/create.js)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -367,7 +367,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
_See code: [src/commands/secret/delete.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/secret/delete.js)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -378,7 +378,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/secret/list.js)_
|
||||
_See code: [src/commands/secret/list.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/secret/list.js)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -405,14 +405,13 @@ OPTIONS
|
||||
--[no-]wait Wait for submission to complete
|
||||
|
||||
DESCRIPTION
|
||||
See how to configure submits with eas.json:
|
||||
https://docs.expo.dev/submit/eas-json/
|
||||
See how to configure submits with eas.json: https://docs.expo.dev/submit/eas-json/
|
||||
|
||||
ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/submit.js)_
|
||||
_See code: [src/commands/submit.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/submit.js)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -431,7 +430,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
_See code: [src/commands/webhook/create.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/webhook/create.js)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -445,7 +444,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
_See code: [src/commands/webhook/delete.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/webhook/delete.js)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -459,7 +458,7 @@ OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
_See code: [src/commands/webhook/list.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/webhook/list.js)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -479,7 +478,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
_See code: [src/commands/webhook/update.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/webhook/update.js)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -493,5 +492,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.34.1/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
_See code: [src/commands/webhook/view.js](https://github.com/expo/eas-cli/blob/v0.36.1/packages/eas-cli/src/commands/webhook/view.js)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,5 +1,5 @@
|
||||
overwrite: true
|
||||
schema: 'https://staging.exp.host/--/graphql'
|
||||
schema: 'https://staging-api.expo.dev/graphql'
|
||||
documents:
|
||||
- 'src/graphql/**/!(*.d).{ts,tsx}'
|
||||
- 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}'
|
||||
|
||||
Generated
+769
-184
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.34.1",
|
||||
"version": "0.36.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
@@ -12,7 +12,7 @@
|
||||
"@expo/config": "6.0.3",
|
||||
"@expo/config-plugins": "4.0.3",
|
||||
"@expo/eas-build-job": "0.2.57",
|
||||
"@expo/eas-json": "^0.34.1",
|
||||
"@expo/eas-json": "^0.36.0",
|
||||
"@expo/json-file": "8.2.33",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.14",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { apiClient, getExpoApiBaseUrl } from '../api';
|
||||
describe('apiClient', () => {
|
||||
it('converts Expo APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl())
|
||||
.post('/--/api/v2/test')
|
||||
.post('/v2/test')
|
||||
.reply(400, {
|
||||
errors: [
|
||||
{
|
||||
@@ -39,7 +39,7 @@ describe('apiClient', () => {
|
||||
});
|
||||
|
||||
it('does not convert non-APIv2 error to ApiV2Error', async () => {
|
||||
nock(getExpoApiBaseUrl()).post('/--/api/v2/test').reply(500, 'Something went wrong');
|
||||
nock(getExpoApiBaseUrl()).post('/v2/test').reply(500, 'Something went wrong');
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Analytics, Event } from './events';
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
|
||||
export async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
attemptEvent: Event;
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { logEvent } from './rudderstackClient';
|
||||
export type Event = BuildEvent | SubmissionEvent;
|
||||
|
||||
export enum SubmissionEvent {
|
||||
SUBMIT_COMMAND = 'submit cli submit command',
|
||||
SUBMIT_COMMAND_ATTEMPT = 'submit cli attempt',
|
||||
SUBMIT_COMMAND_SUCCESS = 'submit cli success',
|
||||
SUBMIT_COMMAND_FAIL = 'submit cli fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'submit cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'submit cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'submit cli gather credentials fail',
|
||||
GATHER_ARCHIVE_ATTEMPT = 'submit cli gather archive attempt',
|
||||
GATHER_ARCHIVE_SUCCESS = 'submit cli gather archive success',
|
||||
GATHER_ARCHIVE_FAIL = 'submit cli gather archive fail',
|
||||
SUBMIT_REQUEST_ATTEMPT = 'submit cli request attempt',
|
||||
SUBMIT_REQUEST_SUCCESS = 'submit cli request success',
|
||||
SUBMIT_REQUEST_FAIL = 'submit cli request fail',
|
||||
}
|
||||
|
||||
export enum BuildEvent {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_ATTEMPT = 'build cli project upload attempt',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_ATTEMPT = 'build cli gather credentials attempt',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_ATTEMPT = 'build cli configure project attempt',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_ATTEMPT = 'build cli build request attempt',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_ATTEMPT = 'build cli credentials sync update local attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_ATTEMPT = 'build cli credentials sync update remote attempt',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export class Analytics {
|
||||
static logEvent(name: Event, properties: Record<string, any>): void {
|
||||
logEvent(name, properties);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,8 +3,8 @@ import os from 'os';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import UserSettings from './user/UserSettings';
|
||||
import { easCliVersion } from './utils/easCli';
|
||||
import UserSettings from '../user/UserSettings';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
|
||||
const PLATFORM_TO_ANALYTICS_PLATFORM: { [platform: string]: string } = {
|
||||
darwin: 'Mac',
|
||||
@@ -4,7 +4,7 @@ import ApiV2Error from './ApiV2Error';
|
||||
import { getAccessToken, getSessionSecret } from './user/sessionStorage';
|
||||
|
||||
export const apiClient = got.extend({
|
||||
prefixUrl: getExpoApiBaseUrl() + '/--/api/v2/',
|
||||
prefixUrl: getExpoApiBaseUrl() + '/v2/',
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
(options: NormalizedOptions) => {
|
||||
@@ -40,11 +40,11 @@ export const apiClient = got.extend({
|
||||
|
||||
export function getExpoApiBaseUrl(): string {
|
||||
if (process.env.EXPO_STAGING) {
|
||||
return `https://staging.exp.host`;
|
||||
return `https://staging-api.expo.dev`;
|
||||
} else if (process.env.EXPO_LOCAL) {
|
||||
return `http://127.0.0.1:3000`;
|
||||
} else {
|
||||
return `https://exp.host`;
|
||||
return `https://api.expo.dev`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { CredentialsSource } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { withAnalyticsAsync } from '../analytics/common';
|
||||
import { BuildEvent } from '../analytics/events';
|
||||
import { BuildFragment, BuildStatus, UploadSessionType } from '../graphql/generated';
|
||||
import { BuildResult } from '../graphql/mutations/BuildMutation';
|
||||
import { BuildQuery } from '../graphql/queries/BuildQuery';
|
||||
@@ -17,8 +19,6 @@ import { getVcsClient } from '../vcs';
|
||||
import { BuildContext } from './context';
|
||||
import { runLocalBuildAsync } from './local';
|
||||
import { MetadataContext, collectMetadataAsync } from './metadata';
|
||||
import { TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
import { printDeprecationWarnings } from './utils/printBuildInfo';
|
||||
import { makeProjectTarballAsync, reviewAndCommitChangesAsync } from './utils/repository';
|
||||
|
||||
@@ -55,15 +55,17 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
const credentialsResult = await withAnalyticsAsync(
|
||||
async () => await builder.ensureCredentialsAsync(ctx),
|
||||
{
|
||||
successEvent: Event.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: Event.GATHER_CREDENTIALS_FAIL,
|
||||
attemptEvent: BuildEvent.GATHER_CREDENTIALS_ATTEMPT,
|
||||
successEvent: BuildEvent.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: BuildEvent.GATHER_CREDENTIALS_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
if (!ctx.skipProjectConfiguration) {
|
||||
await withAnalyticsAsync(async () => await builder.ensureProjectConfiguredAsync(ctx), {
|
||||
successEvent: Event.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: Event.CONFIGURE_PROJECT_FAIL,
|
||||
attemptEvent: BuildEvent.CONFIGURE_PROJECT_ATTEMPT,
|
||||
successEvent: BuildEvent.CONFIGURE_PROJECT_SUCCESS,
|
||||
failureEvent: BuildEvent.CONFIGURE_PROJECT_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
});
|
||||
}
|
||||
@@ -166,8 +168,9 @@ async function uploadProjectAsync<TPlatform extends Platform>(
|
||||
return bucketKey;
|
||||
},
|
||||
{
|
||||
successEvent: Event.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: Event.PROJECT_UPLOAD_FAIL,
|
||||
attemptEvent: BuildEvent.PROJECT_UPLOAD_ATTEMPT,
|
||||
successEvent: BuildEvent.PROJECT_UPLOAD_SUCCESS,
|
||||
failureEvent: BuildEvent.PROJECT_UPLOAD_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
@@ -200,34 +203,14 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
|
||||
return build;
|
||||
},
|
||||
{
|
||||
successEvent: Event.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: Event.BUILD_REQUEST_FAIL,
|
||||
attemptEvent: BuildEvent.BUILD_REQUEST_ATTEMPT,
|
||||
successEvent: BuildEvent.BUILD_REQUEST_SUCCESS,
|
||||
failureEvent: BuildEvent.BUILD_REQUEST_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function withAnalyticsAsync<Result>(
|
||||
fn: () => Promise<Result>,
|
||||
analytics: {
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
trackingCtx: TrackingContext;
|
||||
}
|
||||
): Promise<Result> {
|
||||
try {
|
||||
const result = await fn();
|
||||
Analytics.logEvent(analytics.successEvent, analytics.trackingCtx);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(analytics.failureEvent, {
|
||||
...analytics.trackingCtx,
|
||||
reason: error.message,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForBuildEndAsync(
|
||||
buildIds: string[],
|
||||
{ timeoutSec = 3600, intervalSec = 30 } = {}
|
||||
|
||||
@@ -5,6 +5,8 @@ import JsonFile from '@expo/json-file';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { Analytics, BuildEvent } from '../analytics/events';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { RequestedPlatform } from '../platform';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
@@ -13,8 +15,6 @@ import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import { TrackingContext } from './types';
|
||||
import Analytics, { Event } from './utils/analytics';
|
||||
|
||||
export interface ConfigureContext {
|
||||
user: Actor;
|
||||
@@ -95,7 +95,7 @@ export async function createBuildContextAsync<T extends Platform>({
|
||||
project_type: workflow,
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
|
||||
Analytics.logEvent(BuildEvent.BUILD_COMMAND, trackingCtx);
|
||||
|
||||
return {
|
||||
accountName,
|
||||
|
||||
@@ -20,7 +20,11 @@ export async function ensureIosCredentialsAsync(
|
||||
app: getAppFromContext(buildCtx.credentialsCtx),
|
||||
targets,
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await resolveEntitlementsJsonAsync(buildCtx.projectDir, buildCtx.workflow),
|
||||
entitlements: await resolveEntitlementsJsonAsync(
|
||||
buildCtx.projectDir,
|
||||
buildCtx.workflow,
|
||||
buildCtx.buildProfile.env ?? {}
|
||||
),
|
||||
},
|
||||
distribution: buildCtx.buildProfile.distribution ?? 'store',
|
||||
enterpriseProvisioning: buildCtx.buildProfile.enterpriseProvisioning,
|
||||
|
||||
@@ -1,45 +1,27 @@
|
||||
import { Job } from '@expo/eas-build-job';
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../log';
|
||||
|
||||
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
|
||||
const PLUGIN_PACKAGE_VERSION = '0.0.48';
|
||||
|
||||
export async function runLocalBuildAsync(job: Job): Promise<void> {
|
||||
const execNameOrPath =
|
||||
process.env.EAS_LOCAL_BUILD_PLUGIN_PATH ??
|
||||
(await findWithNodeResolutionAsync()) ??
|
||||
PLUGIN_PACKAGE_NAME;
|
||||
try {
|
||||
const arg = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
await spawnAsync(execNameOrPath, [arg], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
Log.warn(`Could not resolve executable ${execNameOrPath}.`);
|
||||
Log.warn(
|
||||
`Install ${PLUGIN_PACKAGE_NAME} package from npm e.g. ${chalk.bold(
|
||||
`npm install -g ${PLUGIN_PACKAGE_NAME}`
|
||||
)} and make sure it's in PATH.`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const { command, args } = getCommandAndArgs(job);
|
||||
await spawnAsync(command, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
async function findWithNodeResolutionAsync(): Promise<string | undefined> {
|
||||
try {
|
||||
const resolvedPath = path.resolve(
|
||||
path.dirname(require.resolve(PLUGIN_PACKAGE_NAME)),
|
||||
'../bin/run'
|
||||
);
|
||||
if (await fs.pathExists(resolvedPath)) {
|
||||
return resolvedPath;
|
||||
}
|
||||
} catch {}
|
||||
return undefined;
|
||||
function getCommandAndArgs(job: Job): { command: string; args: string[] } {
|
||||
const jobBase64 = Buffer.from(JSON.stringify({ job })).toString('base64');
|
||||
if (process.env.EAS_LOCAL_BUILD_PLUGIN_PATH) {
|
||||
return {
|
||||
command: process.env.EAS_LOCAL_BUILD_PLUGIN_PATH,
|
||||
args: [jobBase64],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: 'npx',
|
||||
args: ['-y', `${PLUGIN_PACKAGE_NAME}@${PLUGIN_PACKAGE_VERSION}`, jobBase64],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,3 @@ export enum BuildDistributionType {
|
||||
INTERNAL = 'internal',
|
||||
SIMULATOR = 'simulator',
|
||||
}
|
||||
|
||||
export type TrackingContext = Record<string, string | number | boolean>;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { logEvent } from '../../analytics';
|
||||
|
||||
export enum Event {
|
||||
BUILD_COMMAND = 'build cli build command',
|
||||
PROJECT_UPLOAD_SUCCESS = 'build cli project upload success',
|
||||
PROJECT_UPLOAD_FAIL = 'build cli project upload fail',
|
||||
GATHER_CREDENTIALS_SUCCESS = 'build cli gather credentials success',
|
||||
GATHER_CREDENTIALS_FAIL = 'build cli gather credentials fail',
|
||||
CONFIGURE_PROJECT_SUCCESS = 'build cli configure project success',
|
||||
CONFIGURE_PROJECT_FAIL = 'build cli configure project fail',
|
||||
BUILD_REQUEST_SUCCESS = 'build cli build request success',
|
||||
BUILD_REQUEST_FAIL = 'build cli build request fail',
|
||||
|
||||
BUILD_STATUS_COMMAND = 'build cli build status',
|
||||
|
||||
CREDENTIALS_SYNC_COMMAND = 'build cli credentials sync command',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_SUCCESS = 'build cli credentials sync update local success',
|
||||
CREDENTIALS_SYNC_UPDATE_LOCAL_FAIL = 'build cli credentials sync update local fail',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_SUCCESS = 'build cli credentials sync update remote success',
|
||||
CREDENTIALS_SYNC_UPDATE_REMOTE_FAIL = 'build cli credentials sync update remote fail',
|
||||
|
||||
ANDROID_KEYSTORE_CREATE = 'build cli credentials keystore create',
|
||||
}
|
||||
|
||||
export default {
|
||||
logEvent(name: Event, properties: Record<string, any>) {
|
||||
logEvent(name, properties);
|
||||
},
|
||||
};
|
||||
@@ -22,7 +22,7 @@ export function getArtifactUrl(artifactId: string): string {
|
||||
|
||||
export function getInternalDistributionInstallUrl(build: BuildFragment): string {
|
||||
if (build.platform === AppPlatform.Ios) {
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/--/api/v2/projects/${
|
||||
return `itms-services://?action=download-manifest;url=${getExpoApiBaseUrl()}/v2/projects/${
|
||||
build.project.id
|
||||
}/builds/${build.id}/manifest.plist`;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
flushAsync as flushAnalyticsAsync,
|
||||
initAsync as initAnalyticsAsync,
|
||||
logEvent,
|
||||
} from '../analytics';
|
||||
} from '../analytics/rudderstackClient';
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync } from '../project/projectUtils';
|
||||
import { getUserAsync } from '../user/User';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics';
|
||||
import { flushAsync, initAsync, logEvent } from '../../analytics/rudderstackClient';
|
||||
import { jester as mockJester } from '../../credentials/__tests__/fixtures-constants';
|
||||
import { getUserAsync } from '../../user/User';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
@@ -10,8 +10,8 @@ TestEasCommand.prototype.authValue = jest.fn().mockImplementation(() => true);
|
||||
|
||||
jest.mock('../../user/User');
|
||||
jest.mock('../../user/actions', () => ({ ensureLoggedInAsync: jest.fn() }));
|
||||
jest.mock('../../analytics', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics');
|
||||
jest.mock('../../analytics/rudderstackClient', () => {
|
||||
const { AnalyticsEvent } = jest.requireActual('../../analytics/rudderstackClient');
|
||||
return {
|
||||
AnalyticsEvent,
|
||||
logEvent: jest.fn(),
|
||||
|
||||
@@ -14,7 +14,7 @@ import { BuildContext, createBuildContextAsync } from '../../build/context';
|
||||
import { prepareIosBuildAsync } from '../../build/ios/build';
|
||||
import { ensureExpoDevClientInstalledForDevClientBuildsAsync } from '../../build/utils/devClient';
|
||||
import { printBuildResults, printLogsUrls } from '../../build/utils/printBuildInfo';
|
||||
import { ensureRepoIsCleanAsync } from '../../build/utils/repository';
|
||||
import { ensureRepoIsCleanAsync, reviewAndCommitChangesAsync } from '../../build/utils/repository';
|
||||
import EasCommand from '../../commandUtils/EasCommand';
|
||||
import { CredentialsContext } from '../../credentials/context';
|
||||
import {
|
||||
@@ -274,7 +274,7 @@ export default class Build extends EasCommand {
|
||||
wait: flags['wait'],
|
||||
clearCache: flags['clear-cache'],
|
||||
json: flags['json'],
|
||||
autoSubmit: flags['auto-submit'],
|
||||
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
|
||||
submitProfile: flags['auto-submit-with-profile'] ?? profile,
|
||||
};
|
||||
}
|
||||
@@ -392,7 +392,8 @@ export async function handleDeprecatedEasJsonAsync(
|
||||
projectDir: string,
|
||||
nonInteractive: boolean
|
||||
): Promise<void> {
|
||||
if (!(await fs.pathExists(EasJsonReader.formatEasJsonPath(projectDir)))) {
|
||||
const easJsonPath = EasJsonReader.formatEasJsonPath(projectDir);
|
||||
if (!(await fs.pathExists(easJsonPath))) {
|
||||
return;
|
||||
}
|
||||
const easJsonReader = new EasJsonReader(projectDir);
|
||||
@@ -432,12 +433,21 @@ export async function handleDeprecatedEasJsonAsync(
|
||||
{ title: 'Allow builds with dirty Git working tree (new default)', value: 'noCommit' },
|
||||
]);
|
||||
|
||||
if (mode === 'requireCommit') {
|
||||
setVcsClient(new GitClient());
|
||||
await ensureRepoIsCleanAsync(nonInteractive);
|
||||
}
|
||||
|
||||
rawEasJson.cli =
|
||||
mode === 'requireCommit'
|
||||
? { version: `>= ${easCliVersion}`, requireCommit: true }
|
||||
: { version: `>= ${easCliVersion}` };
|
||||
await fs.writeJSON(EasJsonReader.formatEasJsonPath(projectDir), rawEasJson, { spaces: 2 });
|
||||
await fs.writeJSON(easJsonPath, rawEasJson, { spaces: 2 });
|
||||
Log.withTick('Updated eas.json');
|
||||
if (mode === 'requireCommit') {
|
||||
setVcsClient(new GitClient());
|
||||
await getVcsClient().trackFileAsync(easJsonPath);
|
||||
await reviewAndCommitChangesAsync('Set cli.requireCommit to true in eas.json', {
|
||||
nonInteractive,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import nullthrows from 'nullthrows';
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../graphql/generated';
|
||||
import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { SetUpBuildCredentials } from './actions/SetUpBuildCredentials';
|
||||
import { AppLookupParams } from './api/GraphqlClient';
|
||||
import { Keystore } from './credentials';
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class AndroidCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getRemoteAsync(): Promise<AndroidCredentials> {
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: this.options.app });
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: this.options.app });
|
||||
const buildCredentials = await setupBuildCredentialsAction.runAsync(this.ctx);
|
||||
return this.toAndroidCredentials(buildCredentials);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export class RemoveFcm {
|
||||
async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
"Deleting an FCM Api Key is a destructive operation. Start the CLI without the '--non-interactive' flag to delete the credentials."
|
||||
"Deleting an FCM API Key is a destructive operation. Start the CLI without the '--non-interactive' flag to delete the credentials."
|
||||
);
|
||||
}
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
||||
@@ -18,7 +18,7 @@ export class RemoveFcm {
|
||||
const fcm = appCredentials?.androidFcm;
|
||||
if (!fcm) {
|
||||
Log.warn(
|
||||
`There is no valid FCM Api Key defined for ${formatProjectFullName(this.app)}, ${
|
||||
`There is no valid FCM API Key defined for ${formatProjectFullName(this.app)}, ${
|
||||
this.app.androidApplicationIdentifier
|
||||
}`
|
||||
);
|
||||
@@ -26,7 +26,7 @@ export class RemoveFcm {
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: 'Permanently delete the FCM Api Key from Expo servers?',
|
||||
message: 'Permanently delete the FCM API Key from Expo servers?',
|
||||
initial: false,
|
||||
});
|
||||
if (!confirm) {
|
||||
@@ -34,6 +34,6 @@ export class RemoveFcm {
|
||||
}
|
||||
|
||||
await ctx.android.deleteFcmAsync(fcm);
|
||||
Log.succeed('FCM Api Key removed');
|
||||
Log.succeed('FCM API Key removed');
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ interface Options {
|
||||
* Sets up Build Credentials for Android
|
||||
* @name: sets up build credentials for the specified configuration. If no name is specified, the default configuration is setup
|
||||
*/
|
||||
export class SetupBuildCredentials {
|
||||
export class SetUpBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment> {
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getKeystoreWithType } from '../utils/keystoreNew';
|
||||
import { BackupKeystore } from './DownloadKeystore';
|
||||
|
||||
export class SetupBuildCredentialsFromCredentialsJson {
|
||||
export class SetUpBuildCredentialsFromCredentialsJson {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AndroidAppBuildCredentialsFragment | null> {
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { AssignGoogleServiceAccountKey } from './AssignGoogleServiceAccountKey';
|
||||
import { CreateGoogleServiceAccountKey } from './CreateGoogleServiceAccountKey';
|
||||
import { UseExistingGoogleServiceAccountKey } from './UseExistingGoogleServiceAccountKey';
|
||||
|
||||
export class SetupGoogleServiceAccountKey {
|
||||
export class SetUpGoogleServiceAccountKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<CommonAndroidAppCredentialsFragment> {
|
||||
@@ -12,7 +12,7 @@ jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemoveFcm, () => {
|
||||
it('removes an FCM Api Key', async () => {
|
||||
it('removes an FCM API Key', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
android: {
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { generateRandomKeystoreAsync } from '../../utils/keystore';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupBuildCredentials } from '../SetupBuildCredentials';
|
||||
import { SetUpBuildCredentials } from '../SetUpBuildCredentials';
|
||||
|
||||
jest.mock('../../../../ora');
|
||||
jest.mock('../../../../project/ensureProjectExists');
|
||||
@@ -17,7 +17,7 @@ jest.mock('../../../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() =
|
||||
jest.mock('../../../../prompts', () => ({ confirmAsync: jest.fn(() => true) }));
|
||||
jest.mock('../../utils/keystore', () => ({ generateRandomKeystoreAsync: jest.fn() }));
|
||||
|
||||
describe('SetupBuildCredentials', () => {
|
||||
describe('SetUpBuildCredentials', () => {
|
||||
beforeEach(() => {
|
||||
asMock(generateRandomKeystoreAsync).mockReset();
|
||||
});
|
||||
@@ -33,7 +33,7 @@ describe('SetupBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore not to be created
|
||||
@@ -48,7 +48,7 @@ describe('SetupBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
// expect keystore to be created
|
||||
@@ -63,7 +63,7 @@ describe('SetupBuildCredentials', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentials({ app: appLookupParams });
|
||||
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
+5
-5
@@ -14,7 +14,7 @@ import {
|
||||
SelectAndroidBuildCredentialsResultType,
|
||||
} from '../../../manager/SelectAndroidBuildCredentials';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupBuildCredentialsFromCredentialsJson } from '../SetupBuildCredentialsFromCredentialsJson';
|
||||
import { SetUpBuildCredentialsFromCredentialsJson } from '../SetUpBuildCredentialsFromCredentialsJson';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
@@ -26,7 +26,7 @@ beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
describe(SetUpBuildCredentialsFromCredentialsJson, () => {
|
||||
it('sets up a new build configuration from credentials.json upon user request', async () => {
|
||||
asMock(SelectAndroidBuildCredentials).mockImplementation(() => {
|
||||
return {
|
||||
@@ -59,7 +59,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
@@ -109,7 +109,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
@@ -132,7 +132,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
const setupBuildCredentialsAction = new SetUpBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError();
|
||||
+5
-5
@@ -10,7 +10,7 @@ import {
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupGoogleServiceAccountKey } from '../SetupGoogleServiceAccountKey';
|
||||
import { SetUpGoogleServiceAccountKey } from '../SetUpGoogleServiceAccountKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('fs');
|
||||
@@ -22,7 +22,7 @@ beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe(SetupGoogleServiceAccountKey, () => {
|
||||
describe(SetUpGoogleServiceAccountKey, () => {
|
||||
it('skips setup when there is a Google Service Account Key already assigned to the project', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
@@ -34,7 +34,7 @@ describe(SetupGoogleServiceAccountKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
await setupGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
|
||||
expect(ctx.android.createGoogleServiceAccountKeyAsync).not.toHaveBeenCalled();
|
||||
@@ -57,7 +57,7 @@ describe(SetupGoogleServiceAccountKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
await setupGoogleServiceAccountKeyAction.runAsync(ctx);
|
||||
|
||||
expect(ctx.android.createGoogleServiceAccountKeyAsync).toHaveBeenCalledTimes(1);
|
||||
@@ -68,7 +68,7 @@ describe(SetupGoogleServiceAccountKey, () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
await expect(setupGoogleServiceAccountKeyAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
@@ -1,31 +1,35 @@
|
||||
import spawnAsync from '@expo/spawn-async';
|
||||
import chalk from 'chalk';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
// (dsokal) We actually want to use node-fetch but the change is in progress.
|
||||
// See https://github.com/expo/eas-cli/issues/32 for context.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Analytics, { Event } from '../../../build/utils/analytics';
|
||||
import { Analytics, BuildEvent } from '../../../analytics/events';
|
||||
import { AndroidKeystoreType } from '../../../graphql/generated';
|
||||
import { KeystoreGenerationUrlMutation } from '../../../graphql/mutations/KeystoreGenerationUrlMutation';
|
||||
import Log from '../../../log';
|
||||
import { ora } from '../../../ora';
|
||||
import { getTmpDirectory } from '../../../utils/paths';
|
||||
import { KeystoreWithType } from '../credentials';
|
||||
|
||||
interface KeystoreParams {
|
||||
keystorePassword: string;
|
||||
keyAlias: string;
|
||||
keyPassword: string;
|
||||
}
|
||||
|
||||
export async function keytoolCommandExistsAsync(): Promise<boolean> {
|
||||
try {
|
||||
await spawnAsync('keytool');
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function ensureKeytoolCommandExistsAsync(): Promise<void> {
|
||||
if (!(await keytoolCommandExistsAsync())) {
|
||||
Log.error('keytool is required to run this command, make sure you have it installed?');
|
||||
Log.warn('keytool is a part of OpenJDK: https://openjdk.java.net/');
|
||||
Log.warn('Also make sure that keytool is in your PATH after installation.');
|
||||
throw new Error('keytool not found');
|
||||
}
|
||||
}
|
||||
|
||||
enum KeystoreCreateStep {
|
||||
@@ -35,7 +39,7 @@ enum KeystoreCreateStep {
|
||||
}
|
||||
|
||||
export async function generateRandomKeystoreAsync(projectId: string): Promise<KeystoreWithType> {
|
||||
const keystoreData = {
|
||||
const keystoreData: KeystoreParams = {
|
||||
keystorePassword: crypto.randomBytes(16).toString('hex'),
|
||||
keyPassword: crypto.randomBytes(16).toString('hex'),
|
||||
keyAlias: crypto.randomBytes(16).toString('hex'),
|
||||
@@ -44,23 +48,38 @@ export async function generateRandomKeystoreAsync(projectId: string): Promise<Ke
|
||||
}
|
||||
|
||||
async function createKeystoreAsync(
|
||||
credentials: {
|
||||
keystorePassword: string;
|
||||
keyAlias: string;
|
||||
keyPassword: string;
|
||||
},
|
||||
keystoreParams: KeystoreParams,
|
||||
projectId: string
|
||||
): Promise<KeystoreWithType> {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Attempt,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
|
||||
try {
|
||||
await ensureKeytoolCommandExistsAsync();
|
||||
let keystore: KeystoreWithType | undefined;
|
||||
let localAttempt = false;
|
||||
if (await keytoolCommandExistsAsync()) {
|
||||
try {
|
||||
localAttempt = true;
|
||||
keystore = await createKeystoreLocallyAsync(keystoreParams);
|
||||
} catch {
|
||||
Log.error('Failed to generate keystore locally. Falling back to cloud generation.');
|
||||
}
|
||||
}
|
||||
if (!keystore) {
|
||||
keystore = await createKeystoreInCloudAsync(keystoreParams, {
|
||||
showKeytoolDetectionMsg: !localAttempt,
|
||||
});
|
||||
}
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Success,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
return keystore;
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
Analytics.logEvent(BuildEvent.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Fail,
|
||||
reason: error.message,
|
||||
@@ -68,7 +87,11 @@ async function createKeystoreAsync(
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function createKeystoreLocallyAsync(
|
||||
keystoreParams: KeystoreParams
|
||||
): Promise<KeystoreWithType> {
|
||||
await fs.mkdirp(getTmpDirectory());
|
||||
const keystorePath = path.join(getTmpDirectory(), `${uuidv4()}-keystore.jks`);
|
||||
try {
|
||||
@@ -78,13 +101,13 @@ async function createKeystoreAsync(
|
||||
'-storetype',
|
||||
'JKS',
|
||||
'-storepass',
|
||||
credentials.keystorePassword,
|
||||
keystoreParams.keystorePassword,
|
||||
'-keypass',
|
||||
credentials.keyPassword,
|
||||
keystoreParams.keyPassword,
|
||||
'-keystore',
|
||||
keystorePath,
|
||||
'-alias',
|
||||
credentials.keyAlias,
|
||||
keystoreParams.keyAlias,
|
||||
'-keyalg',
|
||||
'RSA',
|
||||
'-keysize',
|
||||
@@ -94,27 +117,49 @@ async function createKeystoreAsync(
|
||||
'-dname',
|
||||
`CN=,OU=,O=,L=,S=,C=US`,
|
||||
]);
|
||||
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Success,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
|
||||
return {
|
||||
...credentials,
|
||||
...keystoreParams,
|
||||
keystore: await fs.readFile(keystorePath, 'base64'),
|
||||
type: AndroidKeystoreType.Jks,
|
||||
};
|
||||
} catch (error: any) {
|
||||
Analytics.logEvent(Event.ANDROID_KEYSTORE_CREATE, {
|
||||
project_id: projectId,
|
||||
step: KeystoreCreateStep.Fail,
|
||||
reason: error.message,
|
||||
type: AndroidKeystoreType.Jks,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
await fs.remove(keystorePath);
|
||||
}
|
||||
}
|
||||
|
||||
interface KeystoreServiceResult {
|
||||
keystoreBase64: string;
|
||||
keystorePassword: string;
|
||||
keyPassword: string;
|
||||
keyAlias: string;
|
||||
}
|
||||
|
||||
async function createKeystoreInCloudAsync(
|
||||
keystoreParams: KeystoreParams,
|
||||
{ showKeytoolDetectionMsg = true } = {}
|
||||
): Promise<KeystoreWithType> {
|
||||
if (showKeytoolDetectionMsg) {
|
||||
Log.log(`Detected that you do not have ${chalk.bold('keytool')} installed locally.`);
|
||||
}
|
||||
const spinner = ora('Generating keystore in the cloud...').start();
|
||||
try {
|
||||
const url = await KeystoreGenerationUrlMutation.createKeystoreGenerationUrlAsync();
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(keystoreParams),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const result: KeystoreServiceResult = await response.json();
|
||||
spinner.succeed();
|
||||
return {
|
||||
type: AndroidKeystoreType.Jks,
|
||||
keystore: result.keystoreBase64,
|
||||
keystorePassword: result.keystorePassword,
|
||||
keyAlias: result.keyAlias,
|
||||
keyPassword: result.keyPassword,
|
||||
};
|
||||
} catch (err: any) {
|
||||
spinner.fail();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import { CredentialsContext } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { ensureAllTargetsAreConfigured } from '../credentialsJson/utils';
|
||||
import { getAppFromContext } from './actions/BuildCredentialsUtils';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { SetupPushKey } from './actions/SetupPushKey';
|
||||
import { SetUpBuildCredentials } from './actions/SetUpBuildCredentials';
|
||||
import { SetUpPushKey } from './actions/SetUpPushKey';
|
||||
import { IosCapabilitiesOptions } from './appstore/ensureAppExists';
|
||||
import { App, IosCredentials, Target } from './types';
|
||||
import { isAdHocProfile } from './utils/provisioningProfile';
|
||||
@@ -58,7 +58,7 @@ export default class IosCredentialsProvider {
|
||||
}
|
||||
|
||||
private async getRemoteAsync(): Promise<IosCredentials> {
|
||||
return await new SetupBuildCredentials({
|
||||
return await new SetUpBuildCredentials({
|
||||
app: this.options.app,
|
||||
targets: this.options.targets,
|
||||
distribution: this.options.distribution,
|
||||
@@ -83,7 +83,7 @@ export default class IosCredentialsProvider {
|
||||
parentBundleIdentifier: applicationTarget.parentBundleIdentifier,
|
||||
};
|
||||
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const setupPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(ctx);
|
||||
if (isPushKeySetup) {
|
||||
Log.succeed(
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function provideOrGenerateAscApiKeyAsync(
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<MinimalAscApiKey> {
|
||||
if (ctx.nonInteractive) {
|
||||
return await generateAscApiKeyAsync(ctx, purpose);
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const userProvided = await promptForAscApiKeyAsync(ctx);
|
||||
@@ -73,7 +73,7 @@ export async function provideOrGenerateAscApiKeyAsync(
|
||||
}
|
||||
|
||||
if (!ctx.appStore.authCtx) {
|
||||
Log.warn('Unable to validate App Store Connect Api Key, you are not authenticated with Apple.');
|
||||
Log.warn('Unable to validate App Store Connect API Key, you are not authenticated with Apple.');
|
||||
return userProvided;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function provideOrGenerateAscApiKeyAsync(
|
||||
return userProvided;
|
||||
}
|
||||
const useUserProvided = await confirmAsync({
|
||||
message: `App Store Connect Api Key with ID ${userProvided.keyId} is not valid on Apple's servers. Proceed anyway?`,
|
||||
message: `App Store Connect API Key with ID ${userProvided.keyId} is not valid on Apple's servers. Proceed anyway?`,
|
||||
});
|
||||
if (useUserProvided) {
|
||||
return userProvided;
|
||||
@@ -94,6 +94,7 @@ async function generateAscApiKeyAsync(
|
||||
ctx: CredentialsContext,
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<MinimalAscApiKey> {
|
||||
//console.log('debug');
|
||||
await ctx.appStore.ensureAuthenticatedAsync();
|
||||
const ascApiKey = await ctx.appStore.createAscApiKeyAsync({
|
||||
nickname: getAscApiKeyName(purpose),
|
||||
@@ -129,7 +130,7 @@ async function promptForKeyP8AndIdAsync(): Promise<Pick<AscApiKeyPath, 'keyP8Pat
|
||||
const { keyP8Path } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'keyP8Path',
|
||||
message: 'Path to App Store Connect Api Key:',
|
||||
message: 'Path to App Store Connect API Key:',
|
||||
initial: 'AuthKey_ABCD.p8',
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
validate: async (filePath: string) => {
|
||||
@@ -189,12 +190,12 @@ export async function selectAscApiKeysFromAccountAsync(
|
||||
if (filterDifferentAppleTeam) {
|
||||
const maybeAppleTeamId = ctx.appStore.authCtx?.team.id;
|
||||
Log.warn(
|
||||
`There are no App Store Connect Api Keys in your EAS account${
|
||||
`There are no App Store Connect API Keys in your EAS account${
|
||||
maybeAppleTeamId ? ` matching Apple Team ID: ${maybeAppleTeamId}.` : '.'
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
Log.warn(`There are no App Store Connect Api Keys available in your EAS account.`);
|
||||
Log.warn(`There are no App Store Connect API Keys available in your EAS account.`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -208,7 +209,7 @@ async function selectAscApiKeysAsync(
|
||||
const { chosenAscApiKey } = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'chosenAscApiKey',
|
||||
message: 'Select an Api Key from the list:',
|
||||
message: 'Select an API Key from the list:',
|
||||
choices: sortedAscApiKeys.map(ascApiKey => ({
|
||||
title: formatAscApiKey(ascApiKey),
|
||||
value: ascApiKey,
|
||||
|
||||
@@ -31,7 +31,7 @@ export class AssignAscApiKey {
|
||||
throw new Error(`${purpose} is not yet supported.`);
|
||||
}
|
||||
Log.succeed(
|
||||
`App Store Connect Api Key assigned to ${this.app.projectName}: ${this.app.bundleIdentifier} for ${purpose}.`
|
||||
`App Store Connect API Key assigned to ${this.app.projectName}: ${this.app.bundleIdentifier} for ${purpose}.`
|
||||
);
|
||||
return updatedAppCredentials;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ export class CreateAscApiKey {
|
||||
purpose: AppStoreApiKeyPurpose
|
||||
): Promise<AppStoreConnectApiKeyFragment> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`A new App Store Connect Api Key cannot be created in non-interactive mode.`);
|
||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||
}
|
||||
|
||||
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
||||
const result = await ctx.ios.createAscApiKeyAsync(this.account, ascApiKey);
|
||||
Log.succeed('Created App Store Connect Api Key');
|
||||
Log.succeed('Created App Store Connect API Key');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export class SelectAndRemoveAscApiKey {
|
||||
const selected = await selectAscApiKeysFromAccountAsync(ctx, this.account);
|
||||
if (selected) {
|
||||
await new RemoveAscApiKey(this.account, selected).runAsync(ctx);
|
||||
Log.succeed('Removed App Store Connect Api Key');
|
||||
Log.succeed('Removed App Store Connect API Key');
|
||||
Log.newLine();
|
||||
}
|
||||
}
|
||||
@@ -23,28 +23,28 @@ export class RemoveAscApiKey {
|
||||
|
||||
public async runAsync(ctx: CredentialsContext): Promise<void> {
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(`Cannot remove App Store Connect Api Keys in non-interactive mode.`);
|
||||
throw new Error(`Cannot remove App Store Connect API Keys in non-interactive mode.`);
|
||||
}
|
||||
|
||||
// TODO(quin): add an extra edge on AppStoreConnectApiKey to find the apps using it
|
||||
const confirm = await confirmAsync({
|
||||
message: `Deleting this Api Key may affect your projects that rely on it. Do you want to continue?`,
|
||||
message: `Deleting this API Key may affect your projects that rely on it. Do you want to continue?`,
|
||||
});
|
||||
if (!confirm) {
|
||||
Log.log('Aborting');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log('Removing Api Key');
|
||||
Log.log('Removing API Key');
|
||||
await ctx.ios.deleteAscApiKeyAsync(this.ascApiKey.id);
|
||||
|
||||
let shouldRevoke = false;
|
||||
if (!ctx.nonInteractive) {
|
||||
shouldRevoke = await confirmAsync({
|
||||
message: `Do you also want to revoke this Api Key on the Apple Developer Portal?`,
|
||||
message: `Do you also want to revoke this API Key on the Apple Developer Portal?`,
|
||||
});
|
||||
} else if (ctx.nonInteractive) {
|
||||
Log.log('Skipping Api Key revocation on the Apple Developer Portal.');
|
||||
Log.log('Skipping API Key revocation on the Apple Developer Portal.');
|
||||
}
|
||||
if (shouldRevoke) {
|
||||
await ctx.appStore.revokeAscApiKeyAsync(this.ascApiKey.keyIdentifier);
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ import { validateProvisioningProfileAsync } from '../validators/validateProvisio
|
||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { assignBuildCredentialsAsync, getBuildCredentialsAsync } from './BuildCredentialsUtils';
|
||||
import { chooseDevicesAsync, formatDeviceLabel } from './DeviceUtils';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
import { SetUpDistributionCertificate } from './SetUpDistributionCertificate';
|
||||
|
||||
enum ReuseAction {
|
||||
Yes,
|
||||
@@ -29,11 +29,11 @@ enum ReuseAction {
|
||||
No,
|
||||
}
|
||||
|
||||
export class SetupAdhocProvisioningProfile {
|
||||
export class SetUpAdhocProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
const distCert = await new SetUpDistributionCertificate(
|
||||
this.app,
|
||||
IosDistributionType.AdHoc
|
||||
).runAsync(ctx);
|
||||
@@ -31,10 +31,10 @@ export enum SetupAscApiKeyChoice {
|
||||
export class SetUpAscApiKey {
|
||||
public choices: { title: string; value: string }[] = [
|
||||
{
|
||||
title: '[Choose an existing key]',
|
||||
title: '[Choose an existing ASC API Key]',
|
||||
value: SetupAscApiKeyChoice.USE_EXISTING,
|
||||
},
|
||||
{ title: '[Add a new key]', value: SetupAscApiKeyChoice.GENERATE },
|
||||
{ title: '[Add a new ASC API Key]', value: SetupAscApiKeyChoice.GENERATE },
|
||||
];
|
||||
|
||||
constructor(private app: AppLookupParams, private purpose: AppStoreApiKeyPurpose) {}
|
||||
@@ -42,15 +42,15 @@ export class SetUpAscApiKey {
|
||||
public async runAsync(ctx: CredentialsContext): Promise<CommonIosAppCredentialsFragment> {
|
||||
const isKeySetup = await this.isAscApiKeySetupAsync(ctx, this.purpose);
|
||||
if (isKeySetup) {
|
||||
Log.succeed('App Store Connect Api Key already set up.');
|
||||
Log.succeed('App Store Connect API Key already set up.');
|
||||
return nullthrows(
|
||||
await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(this.app),
|
||||
'iosAppCredentials cannot be null if App Store Connect Api Key is already set up'
|
||||
'iosAppCredentials cannot be null if App Store Connect API Key is already set up'
|
||||
);
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'App Store Connect Api Keys cannot be set up in --non-interactive mode.'
|
||||
'App Store Connect API Keys cannot be set up in --non-interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,11 +88,11 @@ export class SetUpAscApiKey {
|
||||
}
|
||||
const [autoselectedKey] = sortAscApiKeysByUpdatedAtDesc(validKeys);
|
||||
const useAutoselected = await confirmAsync({
|
||||
message: `Reuse this App Store Connect Api Key?\n${formatAscApiKey(autoselectedKey)}`,
|
||||
message: `Reuse this App Store Connect API Key?\n${formatAscApiKey(autoselectedKey)}`,
|
||||
});
|
||||
|
||||
if (useAutoselected) {
|
||||
Log.log(`Using App Store Connect Api Key with ID ${autoselectedKey.keyIdentifier}`);
|
||||
Log.log(`Using App Store Connect API Key with ID ${autoselectedKey.keyIdentifier}`);
|
||||
return autoselectedKey;
|
||||
} else {
|
||||
return null;
|
||||
@@ -105,7 +105,7 @@ export class SetUpAscApiKey {
|
||||
): Promise<boolean> {
|
||||
const appCredentials = await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(this.app);
|
||||
if (purpose !== AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||
throw new Error(`App Store Connect Api Key setup is not yet supported for ${purpose}.`);
|
||||
throw new Error(`App Store Connect API Key setup is not yet supported for ${purpose}.`);
|
||||
}
|
||||
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
||||
}
|
||||
@@ -123,8 +123,8 @@ export class SetUpAscApiKey {
|
||||
const result = await promptAsync({
|
||||
type: 'select',
|
||||
name: 'choice',
|
||||
message: 'Select the App Store Connect Api Key to use for your project:',
|
||||
choices: this.choices,
|
||||
message: 'Select what to use for your project:',
|
||||
choices,
|
||||
});
|
||||
choice = result.choice;
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import { CredentialsContext } from '../../context';
|
||||
import { IosCapabilitiesOptions } from '../appstore/ensureAppExists';
|
||||
import { App, IosAppBuildCredentialsMap, IosCredentials, Target } from '../types';
|
||||
import { displayProjectCredentials } from '../utils/printCredentials';
|
||||
import { SetupTargetBuildCredentials } from './SetupTargetBuildCredentials';
|
||||
import { SetUpTargetBuildCredentials } from './SetUpTargetBuildCredentials';
|
||||
|
||||
interface Options {
|
||||
app: App;
|
||||
@@ -17,7 +17,7 @@ interface Options {
|
||||
iosCapabilitiesOptions?: IosCapabilitiesOptions;
|
||||
}
|
||||
|
||||
export class SetupBuildCredentials {
|
||||
export class SetUpBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosCredentials> {
|
||||
@@ -49,7 +49,7 @@ export class SetupBuildCredentials {
|
||||
} else {
|
||||
Log.newLine();
|
||||
}
|
||||
const action = new SetupTargetBuildCredentials({
|
||||
const action = new SetUpTargetBuildCredentials({
|
||||
...this.options,
|
||||
app: {
|
||||
...this.options.app,
|
||||
+3
-3
@@ -8,9 +8,9 @@ import { readIosCredentialsAsync } from '../../credentialsJson/read';
|
||||
import { IosCredentials } from '../../credentialsJson/types';
|
||||
import { ensureAllTargetsAreConfigured } from '../../credentialsJson/utils';
|
||||
import { App, Target } from '../types';
|
||||
import { SetupTargetBuildCredentialsFromCredentialsJson } from './SetupTargetBuildCredentialsFromCredentialsJson';
|
||||
import { SetUpTargetBuildCredentialsFromCredentialsJson } from './SetUpTargetBuildCredentialsFromCredentialsJson';
|
||||
|
||||
export class SetupBuildCredentialsFromCredentialsJson {
|
||||
export class SetUpBuildCredentialsFromCredentialsJson {
|
||||
constructor(
|
||||
private app: App,
|
||||
private targets: Target[],
|
||||
@@ -32,7 +32,7 @@ export class SetupBuildCredentialsFromCredentialsJson {
|
||||
);
|
||||
Log.newLine();
|
||||
}
|
||||
await new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
await new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
{
|
||||
account: this.app.account,
|
||||
projectName: this.app.projectName,
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
||||
import { CreateDistributionCertificate } from './CreateDistributionCertificate';
|
||||
import { formatDistributionCertificate } from './DistributionCertificateUtils';
|
||||
|
||||
export class SetupDistributionCertificate {
|
||||
export class SetUpDistributionCertificate {
|
||||
private validDistCerts?: AppleDistributionCertificateFragment[];
|
||||
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
+5
-5
@@ -4,8 +4,8 @@ import { promptAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { getAllBuildCredentialsAsync } from './BuildCredentialsUtils';
|
||||
import { SetupAdhocProvisioningProfile } from './SetupAdhocProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
import { SetUpAdhocProvisioningProfile } from './SetUpAdhocProvisioningProfile';
|
||||
import { SetUpProvisioningProfile } from './SetUpProvisioningProfile';
|
||||
|
||||
/**
|
||||
* It's used when setting up credentials for internal distribution but `enterpriseProvisioning` is not set.
|
||||
@@ -14,7 +14,7 @@ import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
* to choose if they want to set up an adhoc or universal distribution provisioning profile. Otherwise, always
|
||||
* set up an adhoc provisioning profile.
|
||||
*/
|
||||
export class SetupInternalProvisioningProfile {
|
||||
export class SetUpInternalProvisioningProfile {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
@@ -82,13 +82,13 @@ export class SetupInternalProvisioningProfile {
|
||||
private async setupAdhocProvisioningProfileAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetupAdhocProvisioningProfile(this.app).runAsync(ctx);
|
||||
return await new SetUpAdhocProvisioningProfile(this.app).runAsync(ctx);
|
||||
}
|
||||
|
||||
private async setupUniversalProvisioningProfileAsync(
|
||||
ctx: CredentialsContext
|
||||
): Promise<IosAppBuildCredentialsFragment> {
|
||||
return await new SetupProvisioningProfile(this.app, IosDistributionType.Enterprise).runAsync(
|
||||
return await new SetUpProvisioningProfile(this.app, IosDistributionType.Enterprise).runAsync(
|
||||
ctx
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -20,12 +20,12 @@ import {
|
||||
import { ConfigureProvisioningProfile } from './ConfigureProvisioningProfile';
|
||||
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
|
||||
import { formatProvisioningProfileFromApple } from './ProvisioningProfileUtils';
|
||||
import { SetupDistributionCertificate } from './SetupDistributionCertificate';
|
||||
import { SetUpDistributionCertificate } from './SetUpDistributionCertificate';
|
||||
|
||||
/**
|
||||
* Sets up either APP_STORE or ENTERPRISE provisioning profiles
|
||||
*/
|
||||
export class SetupProvisioningProfile {
|
||||
export class SetUpProvisioningProfile {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
async areBuildCredentialsSetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
@@ -84,7 +84,7 @@ export class SetupProvisioningProfile {
|
||||
}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
const distCert = await new SetupDistributionCertificate(
|
||||
const distCert = await new SetUpDistributionCertificate(
|
||||
this.app,
|
||||
this.distributionType
|
||||
).runAsync(ctx);
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
selectPushKeyAsync,
|
||||
} from './PushKeyUtils';
|
||||
|
||||
export class SetupPushKey {
|
||||
export class SetUpPushKey {
|
||||
constructor(private app: AppLookupParams) {}
|
||||
|
||||
async isPushKeySetupAsync(ctx: CredentialsContext): Promise<boolean> {
|
||||
+18
-9
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import nullthrows from 'nullthrows';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
|
||||
import { CommonIosAppCredentialsFragment } from '../../../graphql/generated';
|
||||
@@ -8,13 +9,16 @@ import { CredentialsContext } from '../../context';
|
||||
import { UnsupportedCredentialsChoiceError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/GraphqlClient';
|
||||
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||
import { SetupAscApiKey } from './SetupAscApiKey';
|
||||
import { SetUpAscApiKey } from './SetUpAscApiKey';
|
||||
|
||||
const PROMPT_FOR_APP_SPECIFIC_PASSWORD = 'PROMPT_FOR_APP_SPECIFIC_PASSWORD';
|
||||
export class SetupSubmissionCredential {
|
||||
export const PROMPT_FOR_APP_SPECIFIC_PASSWORD = 'PROMPT_FOR_APP_SPECIFIC_PASSWORD';
|
||||
|
||||
export class SetUpSubmissionCredentials {
|
||||
private setupAscApiKeyAction;
|
||||
|
||||
constructor(app: AppLookupParams) {
|
||||
this.setupAscApiKeyAction = new SetupAscApiKey(app, AppStoreApiKeyPurpose.SUBMISSION_SERVICE);
|
||||
this.setupAscApiKeyAction = new SetUpAscApiKey(app, AppStoreApiKeyPurpose.SUBMISSION_SERVICE);
|
||||
// Add this unrelated choice to ASC API Key setup for legacy purposes -- we will deprecate it soon
|
||||
this.setupAscApiKeyAction.choices = this.setupAscApiKeyAction.choices.concat({
|
||||
title: '[Enter an App Specific Password]',
|
||||
value: PROMPT_FOR_APP_SPECIFIC_PASSWORD,
|
||||
@@ -25,11 +29,16 @@ export class SetupSubmissionCredential {
|
||||
ctx: CredentialsContext
|
||||
): Promise<CommonIosAppCredentialsFragment | string> {
|
||||
try {
|
||||
return await this.setupAscApiKeyAction.runAsync(ctx);
|
||||
const iosAppCredentials = await this.setupAscApiKeyAction.runAsync(ctx);
|
||||
const { keyIdentifier, name } = nullthrows(
|
||||
iosAppCredentials.appStoreConnectApiKeyForSubmissions,
|
||||
'ASC API Key must be defined for EAS Submit'
|
||||
);
|
||||
Log.log(`Using API Key ID: ${keyIdentifier}${name ? ` (${name})` : ''}`);
|
||||
return iosAppCredentials;
|
||||
} catch (e) {
|
||||
if (e instanceof UnsupportedCredentialsChoiceError) {
|
||||
const choice = e.choice;
|
||||
if (choice === PROMPT_FOR_APP_SPECIFIC_PASSWORD) {
|
||||
if (e.choice === PROMPT_FOR_APP_SPECIFIC_PASSWORD) {
|
||||
return await this.promptForAppSpecificPasswordAsync();
|
||||
}
|
||||
}
|
||||
@@ -39,6 +48,8 @@ export class SetupSubmissionCredential {
|
||||
|
||||
async promptForAppSpecificPasswordAsync(): Promise<string> {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(`Please enter your Apple app-specific password.`);
|
||||
Log.log(learnMore('https://expo.fyi/apple-app-specific-password'));
|
||||
Log.warn(
|
||||
wrapAnsi(
|
||||
`This option will be deprecated soon. You will still be able to provide your password with the ${chalk.bold(
|
||||
@@ -47,8 +58,6 @@ export class SetupSubmissionCredential {
|
||||
process.stdout.columns || 80
|
||||
)
|
||||
);
|
||||
Log.log(`Please enter your Apple app-specific password.`);
|
||||
Log.log(learnMore('https://expo.fyi/apple-app-specific-password'));
|
||||
|
||||
const { appSpecificPassword } = await promptAsync({
|
||||
name: 'appSpecificPassword',
|
||||
+8
-8
@@ -8,9 +8,9 @@ import Log from '../../../log';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams as GraphQLAppLookupParams } from '../api/GraphqlClient';
|
||||
import { IosCapabilitiesOptions } from '../appstore/ensureAppExists';
|
||||
import { SetupAdhocProvisioningProfile } from './SetupAdhocProvisioningProfile';
|
||||
import { SetupInternalProvisioningProfile } from './SetupInternalProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from './SetupProvisioningProfile';
|
||||
import { SetUpAdhocProvisioningProfile } from './SetUpAdhocProvisioningProfile';
|
||||
import { SetUpInternalProvisioningProfile } from './SetUpInternalProvisioningProfile';
|
||||
import { SetUpProvisioningProfile } from './SetUpProvisioningProfile';
|
||||
|
||||
interface Options {
|
||||
app: GraphQLAppLookupParams;
|
||||
@@ -18,7 +18,7 @@ interface Options {
|
||||
enterpriseProvisioning?: IosEnterpriseProvisioning;
|
||||
iosCapabilitiesOptions?: IosCapabilitiesOptions;
|
||||
}
|
||||
export class SetupTargetBuildCredentials {
|
||||
export class SetUpTargetBuildCredentials {
|
||||
constructor(private options: Options) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<IosAppBuildCredentialsFragment> {
|
||||
@@ -50,17 +50,17 @@ export class SetupTargetBuildCredentials {
|
||||
const { app, distribution, enterpriseProvisioning } = this.options;
|
||||
if (distribution === 'internal') {
|
||||
if (enterpriseProvisioning === 'adhoc') {
|
||||
return await new SetupAdhocProvisioningProfile(app).runAsync(ctx);
|
||||
return await new SetUpAdhocProvisioningProfile(app).runAsync(ctx);
|
||||
} else if (enterpriseProvisioning === 'universal') {
|
||||
return await new SetupProvisioningProfile(
|
||||
return await new SetUpProvisioningProfile(
|
||||
app,
|
||||
GraphQLIosDistributionType.Enterprise
|
||||
).runAsync(ctx);
|
||||
} else {
|
||||
return await new SetupInternalProvisioningProfile(app).runAsync(ctx);
|
||||
return await new SetUpInternalProvisioningProfile(app).runAsync(ctx);
|
||||
}
|
||||
} else {
|
||||
return await new SetupProvisioningProfile(app, GraphQLIosDistributionType.AppStore).runAsync(
|
||||
return await new SetUpProvisioningProfile(app, GraphQLIosDistributionType.AppStore).runAsync(
|
||||
ctx
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ import {
|
||||
getProvisioningProfileAsync,
|
||||
} from './BuildCredentialsUtils';
|
||||
|
||||
export class SetupTargetBuildCredentialsFromCredentialsJson {
|
||||
export class SetUpTargetBuildCredentialsFromCredentialsJson {
|
||||
constructor(
|
||||
private app: AppLookupParams,
|
||||
private distributionType: IosDistributionType,
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
||||
import { testDistCertFragmentOneDependency } from '../../../__tests__/fixtures-ios';
|
||||
import { CredentialsContext } from '../../../context';
|
||||
import { AppLookupParams } from '../../api/GraphqlClient';
|
||||
export class SetupDistributionCertificate {
|
||||
export class SetUpDistributionCertificate {
|
||||
constructor(private app: AppLookupParams, private distributionType: IosDistributionType) {}
|
||||
|
||||
public async runAsync(_ctx: CredentialsContext): Promise<AppleDistributionCertificateFragment> {
|
||||
@@ -6,7 +6,7 @@ import { AssignAscApiKey } from '../AssignAscApiKey';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
|
||||
describe(AssignAscApiKey, () => {
|
||||
it('assigns an App Store Connect Api Key in Interactive Mode for EAS Submit', async () => {
|
||||
it('assigns an App Store Connect API Key in Interactive Mode for EAS Submit', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(CreateAscApiKey, () => {
|
||||
it('creates a App Store Api Key in Interactive Mode', async () => {
|
||||
it('creates a App Store API Key in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
|
||||
@@ -10,7 +10,7 @@ jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(RemoveAscApiKey, () => {
|
||||
it('removes an Asc Api Key', async () => {
|
||||
it('removes an Asc API Key', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const removeAscApiKeyAction = new RemoveAscApiKey(
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { doUDIDsMatch } from '../SetupAdhocProvisioningProfile';
|
||||
import { doUDIDsMatch } from '../SetUpAdhocProvisioningProfile';
|
||||
|
||||
describe(doUDIDsMatch, () => {
|
||||
it('return false if UDIDs do not match', () => {
|
||||
@@ -18,7 +18,7 @@ jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(SetUpAscApiKey, () => {
|
||||
it('skips setting up a ASC Api Key if it is already configured', async () => {
|
||||
it('skips setting up a ASC API Key if it is already configured', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
ios: {
|
||||
@@ -35,12 +35,12 @@ describe(SetUpAscApiKey, () => {
|
||||
);
|
||||
await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new ASC Api Key on expo servers
|
||||
// expect not to create a new ASC API Key on expo servers
|
||||
expect(asMock(ctx.ios.createAscApiKeyAsync).mock.calls.length).toBe(0);
|
||||
// expect configuration not to be updated with a new ASC Api Key
|
||||
// expect configuration not to be updated with a new ASC API Key
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a ASC Api Key, creating a new one if there are no existing ASC Api Keys', async () => {
|
||||
it('sets up a ASC API Key, creating a new one if there are no existing ASC API Keys', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -60,12 +60,12 @@ describe(SetUpAscApiKey, () => {
|
||||
);
|
||||
await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
// expect to create a new ASC Api Key on expo servers
|
||||
// expect to create a new ASC API Key on expo servers
|
||||
expect(asMock(ctx.ios.createAscApiKeyAsync).mock.calls.length).toBe(1);
|
||||
// expect configuration to be updated with a new ASC Api Key
|
||||
// expect configuration to be updated with a new ASC API Key
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('sets up a ASC Api Key, use autoselected ASC Api Key when there are existing keys', async () => {
|
||||
it('sets up a ASC API Key, use autoselected ASC API Key when there are existing keys', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
appStore: {
|
||||
@@ -86,12 +86,12 @@ describe(SetUpAscApiKey, () => {
|
||||
);
|
||||
await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new ASC Api Key on expo servers, we are using the existing one
|
||||
// expect not to create a new ASC API Key on expo servers, we are using the existing one
|
||||
expect(asMock(ctx.ios.createAscApiKeyAsync).mock.calls.length).toBe(0);
|
||||
// expect configuration to be updated with a new ASC Api Key
|
||||
// expect configuration to be updated with a new ASC API Key
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('sets up a ASC Api Key, allowing user to choose existing keys', async () => {
|
||||
it('sets up a ASC API Key, allowing user to choose existing keys', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
ios: {
|
||||
@@ -110,11 +110,28 @@ describe(SetUpAscApiKey, () => {
|
||||
);
|
||||
await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new ASC Api Key on expo servers, we are using the existing one
|
||||
// expect not to create a new ASC API Key on expo servers, we are using the existing one
|
||||
expect(asMock(ctx.ios.createAscApiKeyAsync).mock.calls.length).toBe(0);
|
||||
// expect configuration to be updated with a new ASC Api Key
|
||||
// expect configuration to be updated with a new ASC API Key
|
||||
expect(asMock(ctx.ios.updateIosAppCredentialsAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('works in Non Interactive Mode if ASC Key is configured', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithCommonFieldsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupAscApiKeyAction = new SetUpAscApiKey(
|
||||
appLookupParams,
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
);
|
||||
await expect(setupAscApiKeyAction.runAsync(ctx)).resolves.not.toThrowError();
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
|
||||
+28
-28
@@ -4,13 +4,13 @@ import { promptAsync } from '../../../../prompts';
|
||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAllBuildCredentialsAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupAdhocProvisioningProfile } from '../SetupAdhocProvisioningProfile';
|
||||
import { SetupInternalProvisioningProfile } from '../SetupInternalProvisioningProfile';
|
||||
import { SetupProvisioningProfile } from '../SetupProvisioningProfile';
|
||||
import { SetUpAdhocProvisioningProfile } from '../SetUpAdhocProvisioningProfile';
|
||||
import { SetUpInternalProvisioningProfile } from '../SetUpInternalProvisioningProfile';
|
||||
import { SetUpProvisioningProfile } from '../SetUpProvisioningProfile';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../SetupAdhocProvisioningProfile');
|
||||
jest.mock('../SetupProvisioningProfile');
|
||||
jest.mock('../SetUpAdhocProvisioningProfile');
|
||||
jest.mock('../SetUpProvisioningProfile');
|
||||
jest.mock('../BuildCredentialsUtils', () => ({ getAllBuildCredentialsAsync: jest.fn() }));
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -33,15 +33,15 @@ const testEnterpriseBuildCredentials: IosAppBuildCredentialsFragment = {
|
||||
iosDistributionType: IosDistributionType.Enterprise,
|
||||
};
|
||||
|
||||
describe(SetupInternalProvisioningProfile, () => {
|
||||
describe(SetUpInternalProvisioningProfile, () => {
|
||||
describe('interactive mode', () => {
|
||||
describe('when authenticated with apple', () => {
|
||||
it('runs the SetupAdhocProvisioningProfile action for non-enterprise team', async () => {
|
||||
it('runs the SetUpAdhocProvisioningProfile action for non-enterprise team', async () => {
|
||||
asMock(getAllBuildCredentialsAsync).mockImplementationOnce(() => {
|
||||
const buildCredentials: IosAppBuildCredentialsFragment[] = [];
|
||||
return buildCredentials;
|
||||
});
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -56,7 +56,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
@@ -72,7 +72,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
return buildCredentials;
|
||||
});
|
||||
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -90,7 +90,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
@@ -110,7 +110,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
return buildCredentials;
|
||||
});
|
||||
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -125,14 +125,14 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
expect(runAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs the SetupAdhocProvisioningProfile action when adhoc credentials exist', async () => {
|
||||
it('runs the SetUpAdhocProvisioningProfile action when adhoc credentials exist', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
distributionType: IosDistributionType.Enterprise,
|
||||
}));
|
||||
@@ -141,7 +141,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
return buildCredentials;
|
||||
});
|
||||
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -156,14 +156,14 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
expect(runAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs the SetupProvisioningProfile action when enterprise credentials exist', async () => {
|
||||
it('runs the SetUpProvisioningProfile action when enterprise credentials exist', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
distributionType: IosDistributionType.Enterprise,
|
||||
}));
|
||||
@@ -174,7 +174,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
return buildCredentials;
|
||||
});
|
||||
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -189,7 +189,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
@@ -205,7 +205,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
return buildCredentials;
|
||||
});
|
||||
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -235,7 +235,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
];
|
||||
return buildCredentials;
|
||||
});
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -253,7 +253,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
const buildCredentials: IosAppBuildCredentialsFragment[] = [];
|
||||
return buildCredentials;
|
||||
});
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -264,12 +264,12 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
await expect(action.runAsync(ctx)).rejects.toThrow(/couldn't find any credentials/);
|
||||
});
|
||||
|
||||
it('runs the SetupAdhocProvisioningProfile action when adhoc credentials exist', async () => {
|
||||
it('runs the SetUpAdhocProvisioningProfile action when adhoc credentials exist', async () => {
|
||||
asMock(getAllBuildCredentialsAsync).mockImplementationOnce(() => {
|
||||
const buildCredentials: IosAppBuildCredentialsFragment[] = [testAdhocBuildCredentials];
|
||||
return buildCredentials;
|
||||
});
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -279,19 +279,19 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpAdhocProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
expect(runAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs the SetupProvisioningProfile action when enterprise credentials exist', async () => {
|
||||
it('runs the SetUpProvisioningProfile action when enterprise credentials exist', async () => {
|
||||
asMock(getAllBuildCredentialsAsync).mockImplementationOnce(() => {
|
||||
const buildCredentials: IosAppBuildCredentialsFragment[] = [testEnterpriseBuildCredentials];
|
||||
return buildCredentials;
|
||||
});
|
||||
const action = new SetupInternalProvisioningProfile({
|
||||
const action = new SetUpInternalProvisioningProfile({
|
||||
account: { id: 'account-id', name: 'account-name' },
|
||||
bundleIdentifier: 'com.expo.test',
|
||||
projectName: 'testproject',
|
||||
@@ -301,7 +301,7 @@ describe(SetupInternalProvisioningProfile, () => {
|
||||
});
|
||||
|
||||
const runAsync = jest.fn();
|
||||
SetupProvisioningProfile.prototype.runAsync = runAsync;
|
||||
SetUpProvisioningProfile.prototype.runAsync = runAsync;
|
||||
|
||||
await action.runAsync(ctx);
|
||||
|
||||
+8
-8
@@ -16,16 +16,16 @@ import {
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { SetupProvisioningProfile } from '../SetupProvisioningProfile';
|
||||
import { SetUpProvisioningProfile } from '../SetUpProvisioningProfile';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
jest.mock('../SetupDistributionCertificate');
|
||||
jest.mock('../SetUpDistributionCertificate');
|
||||
jest.mock('../ConfigureProvisioningProfile');
|
||||
jest.mock('../CreateProvisioningProfile');
|
||||
jest.mock('../../validators/validateProvisioningProfile');
|
||||
|
||||
describe('SetupProvisioningProfile', () => {
|
||||
describe('SetUpProvisioningProfile', () => {
|
||||
it('repairs existing Provisioning Profile with bad build credentials in Interactive Mode', async () => {
|
||||
asMock(validateProvisioningProfileAsync).mockImplementation(() => false);
|
||||
const ctx = createCtxMock({
|
||||
@@ -54,7 +54,7 @@ describe('SetupProvisioningProfile', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
@@ -84,7 +84,7 @@ describe('SetupProvisioningProfile', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
@@ -114,7 +114,7 @@ describe('SetupProvisioningProfile', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
@@ -140,7 +140,7 @@ describe('SetupProvisioningProfile', () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
@@ -157,7 +157,7 @@ describe('SetupProvisioningProfile', () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupProvisioningProfileAction = new SetupProvisioningProfile(
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
+6
-6
@@ -5,12 +5,12 @@ import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appsto
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getNewIosApiMock, testPushKey, testTargets } from '../../../__tests__/fixtures-ios';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { SetupPushKey } from '../SetupPushKey';
|
||||
import { SetUpPushKey } from '../SetUpPushKey';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(SetupPushKey, () => {
|
||||
describe(SetUpPushKey, () => {
|
||||
it('skips setting up a Push Key if it is already configured', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
@@ -20,7 +20,7 @@ describe(SetupPushKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const setupPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new push key on expo servers
|
||||
@@ -38,7 +38,7 @@ describe(SetupPushKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const setupPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect to create a new push key on expo servers
|
||||
@@ -62,7 +62,7 @@ describe(SetupPushKey, () => {
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const setupPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
await setupPushKeyAction.runAsync(ctx);
|
||||
|
||||
// expect not to create a new push key on expo servers, we are using the existing one
|
||||
@@ -75,7 +75,7 @@ describe(SetupPushKey, () => {
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const createPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const createPushKeyAction = new SetUpPushKey(appLookupParams);
|
||||
await expect(createPushKeyAction.runAsync(ctx)).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { asMock } from '../../../../__tests__/utils';
|
||||
import { findApplicationTarget } from '../../../../project/ios/target';
|
||||
import { confirmAsync, promptAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import {
|
||||
getNewIosApiMock,
|
||||
testCommonIosAppCredentialsFragment,
|
||||
testTargets,
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { SetUpAscApiKey } from '../SetUpAscApiKey';
|
||||
import {
|
||||
PROMPT_FOR_APP_SPECIFIC_PASSWORD,
|
||||
SetUpSubmissionCredentials,
|
||||
} from '../SetUpSubmissionCredentials';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
|
||||
describe(SetUpSubmissionCredentials, () => {
|
||||
it('allows user to enter an App Specific Password', async () => {
|
||||
asMock(promptAsync)
|
||||
.mockImplementationOnce(() => ({
|
||||
choice: PROMPT_FOR_APP_SPECIFIC_PASSWORD,
|
||||
}))
|
||||
.mockImplementationOnce(() => ({
|
||||
appSpecificPassword: 'super secret',
|
||||
}));
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupAscApiKeyAction = new SetUpSubmissionCredentials(appLookupParams);
|
||||
const asp = await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
// prompt to choose ASP, then prompt to input ASP
|
||||
expect(asMock(promptAsync).mock.calls.length).toBe(2);
|
||||
expect(asp).toBe('super secret');
|
||||
});
|
||||
it('returns an ASC API key', async () => {
|
||||
try {
|
||||
jest
|
||||
.spyOn(SetUpAscApiKey.prototype, 'runAsync')
|
||||
.mockImplementation(async _ctx => testCommonIosAppCredentialsFragment);
|
||||
const ctx = createCtxMock();
|
||||
const appLookupParams = getAppLookupParamsFromContext(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupAscApiKeyAction = new SetUpSubmissionCredentials(appLookupParams);
|
||||
const asc = await setupAscApiKeyAction.runAsync(ctx);
|
||||
|
||||
expect(asc).toEqual(testCommonIosAppCredentialsFragment);
|
||||
} finally {
|
||||
jest.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
});
|
||||
+6
-6
@@ -15,7 +15,7 @@ import {
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { IosTargetCredentials } from '../../../credentialsJson/types';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { SetupTargetBuildCredentialsFromCredentialsJson } from '../SetupTargetBuildCredentialsFromCredentialsJson';
|
||||
import { SetUpTargetBuildCredentialsFromCredentialsJson } from '../SetUpTargetBuildCredentialsFromCredentialsJson';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../credentialsJson/read');
|
||||
@@ -25,7 +25,7 @@ beforeEach(() => {
|
||||
asMock(confirmAsync).mockImplementation(() => true);
|
||||
});
|
||||
|
||||
describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
describe('SetUpTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
const targetCredentials: IosTargetCredentials = {
|
||||
distributionCertificate: {
|
||||
certificateP12: testDistCert.certP12,
|
||||
@@ -53,7 +53,7 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupBuildCredentialsFromCredentialsJsonAction =
|
||||
new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore,
|
||||
targetCredentials
|
||||
@@ -82,7 +82,7 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupBuildCredentialsFromCredentialsJsonAction =
|
||||
new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore,
|
||||
targetCredentials
|
||||
@@ -115,7 +115,7 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupBuildCredentialsFromCredentialsJsonAction =
|
||||
new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore,
|
||||
targetCredentials
|
||||
@@ -149,7 +149,7 @@ describe('SetupTargetBuildCredentialsFromCredentialsJson', () => {
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx, findApplicationTarget(testTargets));
|
||||
const setupBuildCredentialsFromCredentialsJsonAction =
|
||||
new SetupTargetBuildCredentialsFromCredentialsJson(
|
||||
new SetUpTargetBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams,
|
||||
IosDistributionType.AppStore,
|
||||
targetCredentials
|
||||
@@ -6,14 +6,14 @@ import { AscApiKey, AscApiKeyInfo } from './Credentials.types';
|
||||
import { AuthCtx, getRequestContext } from './authenticate';
|
||||
|
||||
export async function listAscApiKeysAsync(authCtx: AuthCtx): Promise<AscApiKeyInfo[]> {
|
||||
const spinner = ora(`Fetching App Store Connect Api Keys.`).start();
|
||||
const spinner = ora(`Fetching App Store Connect API Keys.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const keys = await ApiKey.getAsync(context);
|
||||
spinner.succeed(`Fetched App Store Connect Api Keys.`);
|
||||
spinner.succeed(`Fetched App Store Connect API Keys.`);
|
||||
return keys.map(key => getAscApiKeyInfo(key, authCtx));
|
||||
} catch (error) {
|
||||
spinner.fail(`Failed to fetch App Store Connect Api Keys.`);
|
||||
spinner.fail(`Failed to fetch App Store Connect API Keys.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ export async function getAscApiKeyAsync(
|
||||
authCtx: AuthCtx,
|
||||
keyId: string
|
||||
): Promise<AscApiKeyInfo | null> {
|
||||
const spinner = ora(`Fetching App Store Connect Api Key.`).start();
|
||||
const spinner = ora(`Fetching App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
|
||||
spinner.succeed(`Fetched App Store Connect Api Key (ID: ${keyId}).`);
|
||||
spinner.succeed(`Fetched App Store Connect API Key (ID: ${keyId}).`);
|
||||
return getAscApiKeyInfo(apiKey, authCtx);
|
||||
} catch (error: any) {
|
||||
const message = error?.message ?? '';
|
||||
@@ -35,7 +35,7 @@ export async function getAscApiKeyAsync(
|
||||
return null;
|
||||
}
|
||||
Log.error(error);
|
||||
spinner.fail(`Failed to fetch App Store Connect Api Key.`);
|
||||
spinner.fail(`Failed to fetch App Store Connect API Key.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export async function createAscApiKeyAsync(
|
||||
keyType,
|
||||
}: Partial<Pick<ApiKeyProps, 'nickname' | 'roles' | 'allAppsVisible' | 'keyType'>>
|
||||
): Promise<AscApiKey> {
|
||||
const spinner = ora(`Creating App Store Connect Api Key.`).start();
|
||||
const spinner = ora(`Creating App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const key = await ApiKey.createAsync(context, {
|
||||
@@ -74,13 +74,13 @@ export async function createAscApiKeyAsync(
|
||||
}
|
||||
// this object has more optional parameters populated
|
||||
const fullKey = await ApiKey.infoAsync(context, { id: key.id });
|
||||
spinner.succeed(`Created App Store Connect Api Key.`);
|
||||
spinner.succeed(`Created App Store Connect API Key.`);
|
||||
return {
|
||||
...getAscApiKeyInfo(fullKey, authCtx),
|
||||
keyP8,
|
||||
};
|
||||
} catch (err: any) {
|
||||
spinner.fail('Failed to create App Store Connect Api Key.');
|
||||
spinner.fail('Failed to create App Store Connect API Key.');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -89,16 +89,16 @@ export async function revokeAscApiKeyAsync(
|
||||
authCtx: AuthCtx,
|
||||
keyId: string
|
||||
): Promise<AscApiKeyInfo> {
|
||||
const spinner = ora(`Revoking App Store Connect Api Key.`).start();
|
||||
const spinner = ora(`Revoking App Store Connect API Key.`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
|
||||
const revokedKey = await apiKey.revokeAsync();
|
||||
spinner.succeed(`Revoked App Store Connect Api Key.`);
|
||||
spinner.succeed(`Revoked App Store Connect API Key.`);
|
||||
return getAscApiKeyInfo(revokedKey, authCtx);
|
||||
} catch (error) {
|
||||
Log.error(error);
|
||||
spinner.fail(`Failed to revoke App Store Connect Api Key.`);
|
||||
spinner.fail(`Failed to revoke App Store Connect API Key.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,38 @@ import plist from '@expo/plist';
|
||||
import { getPrebuildConfig } from '@expo/prebuild-config';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export async function getManagedEntitlementsJsonAsync(projectDir: string): Promise<JSONObject> {
|
||||
let { exp } = getPrebuildConfig(projectDir, { platforms: ['ios'] });
|
||||
export async function getManagedEntitlementsJsonAsync(
|
||||
projectDir: string,
|
||||
env: Record<string, string>
|
||||
): Promise<JSONObject> {
|
||||
const originalProcessEnv: NodeJS.ProcessEnv = process.env;
|
||||
try {
|
||||
process.env = {
|
||||
...process.env,
|
||||
...env,
|
||||
};
|
||||
const { exp } = getPrebuildConfig(projectDir, { platforms: ['ios'] });
|
||||
|
||||
exp = await compileModsAsync(exp, {
|
||||
projectRoot: projectDir,
|
||||
platforms: ['ios'],
|
||||
introspect: true,
|
||||
});
|
||||
return exp.ios?.entitlements || {};
|
||||
const expWithMods = await compileModsAsync(exp, {
|
||||
projectRoot: projectDir,
|
||||
platforms: ['ios'],
|
||||
introspect: true,
|
||||
});
|
||||
return expWithMods.ios?.entitlements || {};
|
||||
} finally {
|
||||
process.env = originalProcessEnv;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveEntitlementsJsonAsync(
|
||||
projectDir: string,
|
||||
workflow: Workflow
|
||||
workflow: Workflow,
|
||||
env: Record<string, string>
|
||||
): Promise<JSONObject> {
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
return (await getEntitlementsJsonAsync(projectDir)) || {};
|
||||
} else if (workflow === Workflow.MANAGED) {
|
||||
return await getManagedEntitlementsJsonAsync(projectDir);
|
||||
return await getManagedEntitlementsJsonAsync(projectDir, env);
|
||||
} else {
|
||||
throw new Error(`Unknown workflow: ${workflow}`);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export interface AscApiKeyPath {
|
||||
}
|
||||
|
||||
export const ascApiKeyIdSchema: CredentialSchema<Pick<MinimalAscApiKey, 'keyId'>> = {
|
||||
name: 'App Store Connect Api Key',
|
||||
name: 'App Store Connect API Key',
|
||||
questions: [
|
||||
{
|
||||
field: 'keyId',
|
||||
@@ -130,7 +130,7 @@ export const ascApiKeyIdSchema: CredentialSchema<Pick<MinimalAscApiKey, 'keyId'>
|
||||
};
|
||||
|
||||
export const ascApiKeyIssuerIdSchema: CredentialSchema<Pick<MinimalAscApiKey, 'issuerId'>> = {
|
||||
name: 'App Store Connect Api Key',
|
||||
name: 'App Store Connect API Key',
|
||||
questions: [
|
||||
{
|
||||
field: 'issuerId',
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Developer Portal ID 9839M6AY8W
|
||||
Apple Team 77KQ969CHE
|
||||
Updated 6 months ago
|
||||
|
||||
App Store Connect Api Key
|
||||
App Store Connect API Key
|
||||
Developer Portal ID NYN468XNN3
|
||||
Name Expo Submissions pl94k8_lnS
|
||||
Issuer ID 69a6de93-e4e4-47e3-e053-5b8c7c11a4d1
|
||||
|
||||
@@ -239,7 +239,7 @@ function displayAscApiKey(
|
||||
maybeAscApiKey: AppStoreConnectApiKeyFragment | null,
|
||||
fields: { label: string; value: string }[]
|
||||
): void {
|
||||
fields.push({ label: 'App Store Connect Api Key', value: '' });
|
||||
fields.push({ label: 'App Store Connect API Key', value: '' });
|
||||
if (maybeAscApiKey) {
|
||||
const { keyIdentifier, issuerIdentifier, appleTeam, name, roles, updatedAt } = maybeAscApiKey;
|
||||
fields.push({ label: 'Developer Portal ID', value: keyIdentifier });
|
||||
|
||||
@@ -25,9 +25,9 @@ export enum AndroidActionType {
|
||||
CreateGsaKey,
|
||||
UseExistingGsaKey,
|
||||
RemoveGsaKey,
|
||||
SetupGsaKey,
|
||||
SetUpGsaKey,
|
||||
UpdateCredentialsJson,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
SetUpBuildCredentialsFromCredentialsJson,
|
||||
}
|
||||
|
||||
export enum IosActionType {
|
||||
@@ -37,14 +37,14 @@ export enum IosActionType {
|
||||
ManageAscApiKey,
|
||||
GoBackToCaller,
|
||||
GoBackToHighLevelActions,
|
||||
SetupBuildCredentials,
|
||||
SetupBuildCredentialsFromCredentialsJson,
|
||||
SetUpBuildCredentials,
|
||||
SetUpBuildCredentialsFromCredentialsJson,
|
||||
UpdateCredentialsJson,
|
||||
UseExistingDistributionCertificate,
|
||||
RemoveProvisioningProfile,
|
||||
CreateDistributionCertificate,
|
||||
RemoveDistributionCertificate,
|
||||
SetupPushKey,
|
||||
SetUpPushKey,
|
||||
CreatePushKey,
|
||||
UseExistingPushKey,
|
||||
RemovePushKey,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const highLevelActions: ActionInfo[] = [
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.ManageFcm,
|
||||
title: 'Push Notifications: Manage your FCM Api Key',
|
||||
title: 'Push Notifications: Manage your FCM API Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
@@ -35,7 +35,7 @@ export const credentialsJsonActions: ActionInfo[] = [
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
value: AndroidActionType.SetUpBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
@@ -72,12 +72,12 @@ export const buildCredentialsActions: ActionInfo[] = [
|
||||
export const fcmActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.CreateFcm,
|
||||
title: 'Upload an FCM Api Key',
|
||||
title: 'Upload an FCM API Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: AndroidActionType.RemoveFcm,
|
||||
title: 'Delete your FCM Api Key',
|
||||
title: 'Delete your FCM API Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
@@ -89,8 +89,8 @@ export const fcmActions: ActionInfo[] = [
|
||||
|
||||
export const gsaKeyActions: ActionInfo[] = [
|
||||
{
|
||||
value: AndroidActionType.SetupGsaKey,
|
||||
title: 'Setup a Google Service Account Key',
|
||||
value: AndroidActionType.SetUpGsaKey,
|
||||
title: 'Set up a Google Service Account Key',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ export const highLevelActions: ActionInfo[] = [
|
||||
},
|
||||
{
|
||||
value: IosActionType.ManageAscApiKey,
|
||||
title: 'App Store Connect: Manage your Api Key',
|
||||
title: 'App Store Connect: Manage your API Key',
|
||||
scope: Scope.Manager,
|
||||
},
|
||||
{
|
||||
@@ -36,7 +36,7 @@ export const credentialsJsonActions: ActionInfo[] = [
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.SetupBuildCredentialsFromCredentialsJson,
|
||||
value: IosActionType.SetUpBuildCredentialsFromCredentialsJson,
|
||||
title: 'Upload credentials from credentials.json to EAS',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
@@ -50,8 +50,8 @@ export const credentialsJsonActions: ActionInfo[] = [
|
||||
export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
value: IosActionType.SetupPushKey,
|
||||
title: 'Setup your project to use Push Notifications',
|
||||
value: IosActionType.SetUpPushKey,
|
||||
title: 'Set up your project to use Push Notifications',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
@@ -81,22 +81,22 @@ export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
||||
return [
|
||||
{
|
||||
value: IosActionType.SetUpAscApiKeyForSubmissions,
|
||||
title: 'Set up your project to use an Api Key for EAS Submit',
|
||||
title: 'Set up your project to use an API Key for EAS Submit',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.UseExistingAscApiKeyForSubmissions,
|
||||
title: 'Use an existing Api Key for EAS Submit',
|
||||
title: 'Use an existing API Key for EAS Submit',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
{
|
||||
value: IosActionType.CreateAscApiKeyForSubmissions,
|
||||
title: 'Add a new Api Key For EAS Submit',
|
||||
title: 'Add a new API Key For EAS Submit',
|
||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
||||
},
|
||||
{
|
||||
value: IosActionType.RemoveAscApiKey,
|
||||
title: 'Delete an Api Key',
|
||||
title: 'Delete an API Key',
|
||||
scope: Scope.Account,
|
||||
},
|
||||
{
|
||||
@@ -112,7 +112,7 @@ export function getBuildCredentialsActions(ctx: CredentialsContext): ActionInfo[
|
||||
{
|
||||
// This command will be triggered during build to ensure all credentials are ready
|
||||
// I'm leaving it here for now to simplify testing
|
||||
value: IosActionType.SetupBuildCredentials,
|
||||
value: IosActionType.SetUpBuildCredentials,
|
||||
title: 'All: Set up all the required credentials to build your project',
|
||||
scope: Scope.Project,
|
||||
},
|
||||
|
||||
@@ -25,8 +25,8 @@ import { DownloadKeystore } from '../android/actions/DownloadKeystore';
|
||||
import { RemoveFcm } from '../android/actions/RemoveFcm';
|
||||
import { SelectAndRemoveGoogleServiceAccountKey } from '../android/actions/RemoveGoogleServiceAccountKey';
|
||||
import { RemoveKeystore } from '../android/actions/RemoveKeystore';
|
||||
import { SetupBuildCredentialsFromCredentialsJson } from '../android/actions/SetupBuildCredentialsFromCredentialsJson';
|
||||
import { SetupGoogleServiceAccountKey } from '../android/actions/SetupGoogleServiceAccountKey';
|
||||
import { SetUpBuildCredentialsFromCredentialsJson } from '../android/actions/SetUpBuildCredentialsFromCredentialsJson';
|
||||
import { SetUpGoogleServiceAccountKey } from '../android/actions/SetUpGoogleServiceAccountKey';
|
||||
import { UpdateCredentialsJson } from '../android/actions/UpdateCredentialsJson';
|
||||
import { UseExistingGoogleServiceAccountKey } from '../android/actions/UseExistingGoogleServiceAccountKey';
|
||||
import {
|
||||
@@ -228,8 +228,8 @@ export class ManageAndroid {
|
||||
}
|
||||
} else if (action === AndroidActionType.RemoveGsaKey) {
|
||||
await new SelectAndRemoveGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx);
|
||||
} else if (action === AndroidActionType.SetupGsaKey) {
|
||||
await new SetupGoogleServiceAccountKey(appLookupParams).runAsync(ctx);
|
||||
} else if (action === AndroidActionType.SetUpGsaKey) {
|
||||
await new SetUpGoogleServiceAccountKey(appLookupParams).runAsync(ctx);
|
||||
} else if (action === AndroidActionType.UpdateCredentialsJson) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
@@ -237,8 +237,8 @@ export class ManageAndroid {
|
||||
if (buildCredentials) {
|
||||
await new UpdateCredentialsJson().runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === AndroidActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(appLookupParams).runAsync(ctx);
|
||||
} else if (action === AndroidActionType.SetUpBuildCredentialsFromCredentialsJson) {
|
||||
await new SetUpBuildCredentialsFromCredentialsJson(appLookupParams).runAsync(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,12 +35,12 @@ import { SelectAndRemoveAscApiKey } from '../ios/actions/RemoveAscApiKey';
|
||||
import { SelectAndRemoveDistributionCertificate } from '../ios/actions/RemoveDistributionCertificate';
|
||||
import { RemoveProvisioningProfiles } from '../ios/actions/RemoveProvisioningProfile';
|
||||
import { SelectAndRemovePushKey } from '../ios/actions/RemovePushKey';
|
||||
import { SetUpAdhocProvisioningProfile } from '../ios/actions/SetUpAdhocProvisioningProfile';
|
||||
import { SetUpAscApiKey } from '../ios/actions/SetUpAscApiKey';
|
||||
import { SetupAdhocProvisioningProfile } from '../ios/actions/SetupAdhocProvisioningProfile';
|
||||
import { SetupBuildCredentials } from '../ios/actions/SetupBuildCredentials';
|
||||
import { SetupBuildCredentialsFromCredentialsJson } from '../ios/actions/SetupBuildCredentialsFromCredentialsJson';
|
||||
import { SetupProvisioningProfile } from '../ios/actions/SetupProvisioningProfile';
|
||||
import { SetupPushKey } from '../ios/actions/SetupPushKey';
|
||||
import { SetUpBuildCredentials } from '../ios/actions/SetUpBuildCredentials';
|
||||
import { SetUpBuildCredentialsFromCredentialsJson } from '../ios/actions/SetUpBuildCredentialsFromCredentialsJson';
|
||||
import { SetUpProvisioningProfile } from '../ios/actions/SetUpProvisioningProfile';
|
||||
import { SetUpPushKey } from '../ios/actions/SetUpPushKey';
|
||||
import { UpdateCredentialsJson } from '../ios/actions/UpdateCredentialsJson';
|
||||
import { AppLookupParams } from '../ios/api/GraphqlClient';
|
||||
import { getManagedEntitlementsJsonAsync } from '../ios/appstore/entitlements';
|
||||
@@ -222,14 +222,17 @@ export class ManageIos {
|
||||
buildProfile: IosBuildProfile,
|
||||
action: IosActionType
|
||||
): Promise<void> {
|
||||
if (action === IosActionType.SetupBuildCredentials) {
|
||||
await new SetupBuildCredentials({
|
||||
if (action === IosActionType.SetUpBuildCredentials) {
|
||||
await new SetUpBuildCredentials({
|
||||
app,
|
||||
targets,
|
||||
distribution: buildProfile.distribution,
|
||||
enterpriseProvisioning: buildProfile.enterpriseProvisioning,
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await getManagedEntitlementsJsonAsync(ctx.projectDir),
|
||||
entitlements: await getManagedEntitlementsJsonAsync(
|
||||
ctx.projectDir,
|
||||
buildProfile.env ?? {}
|
||||
),
|
||||
},
|
||||
}).runAsync(ctx);
|
||||
return;
|
||||
@@ -239,8 +242,8 @@ export class ManageIos {
|
||||
buildProfile
|
||||
).runAsync(ctx);
|
||||
|
||||
if (action === IosActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(app, targets, distributionType).runAsync(
|
||||
if (action === IosActionType.SetUpBuildCredentialsFromCredentialsJson) {
|
||||
await new SetUpBuildCredentialsFromCredentialsJson(app, targets, distributionType).runAsync(
|
||||
ctx
|
||||
);
|
||||
return;
|
||||
@@ -305,15 +308,15 @@ export class ManageIos {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IosActionType.SetupPushKey: {
|
||||
const setupPushKeyAction = await new SetupPushKey(appLookupParams);
|
||||
case IosActionType.SetUpPushKey: {
|
||||
const setupPushKeyAction = await new SetUpPushKey(appLookupParams);
|
||||
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(ctx);
|
||||
if (isPushKeySetup) {
|
||||
Log.log(
|
||||
`Push Key is already set up for ${appLookupParams.projectName} ${appLookupParams.bundleIdentifier}`
|
||||
);
|
||||
} else {
|
||||
await new SetupPushKey(appLookupParams).runAsync(ctx);
|
||||
await new SetUpPushKey(appLookupParams).runAsync(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -360,7 +363,7 @@ export class ManageIos {
|
||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
||||
);
|
||||
const confirm = await confirmAsync({
|
||||
message: `Do you want ${appLookupParams.projectName} to use the new Api Key?`,
|
||||
message: `Do you want ${appLookupParams.projectName} to use the new API Key?`,
|
||||
});
|
||||
if (confirm) {
|
||||
await new AssignAscApiKey(appLookupParams).runAsync(
|
||||
@@ -385,11 +388,11 @@ export class ManageIos {
|
||||
Log.log(`Setting up ${appLookupParams.projectName} to use Distribution Certificate`);
|
||||
Log.log(`Creating provisioning profile...`);
|
||||
if (distributionType === IosDistributionTypeGraphql.AdHoc) {
|
||||
return await new SetupAdhocProvisioningProfile(
|
||||
return await new SetUpAdhocProvisioningProfile(
|
||||
appLookupParams
|
||||
).runWithDistributionCertificateAsync(ctx, distCert);
|
||||
} else {
|
||||
return await new SetupProvisioningProfile(
|
||||
return await new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
distributionType
|
||||
).createAndAssignProfileAsync(ctx, distCert);
|
||||
|
||||
@@ -30,7 +30,7 @@ type SessionHeaders = {
|
||||
};
|
||||
|
||||
export const graphqlClient = createUrqlClient({
|
||||
url: getExpoApiBaseUrl() + '/--/graphql',
|
||||
url: getExpoApiBaseUrl() + '/graphql',
|
||||
exchanges: [
|
||||
dedupExchange,
|
||||
cacheExchange,
|
||||
|
||||
+158
-55
@@ -41,7 +41,7 @@ export type RootQuery = {
|
||||
appleDeviceRegistrationRequest: AppleDeviceRegistrationRequestQuery;
|
||||
/** Top-level query object for querying Apple Teams. */
|
||||
appleTeam: AppleTeamQuery;
|
||||
app?: Maybe<AppQuery>;
|
||||
app: AppQuery;
|
||||
/**
|
||||
* Look up app by app id
|
||||
* @deprecated Use 'byId' field under 'app'.
|
||||
@@ -60,6 +60,8 @@ export type RootQuery = {
|
||||
clientBuilds: ClientBuildQuery;
|
||||
/** Top-level query object for querying Experimentation configuration. */
|
||||
experimentation: ExperimentationQuery;
|
||||
/** Top-level query object for querying Stripe Invoices. */
|
||||
invoice: InvoiceQuery;
|
||||
project: ProjectQuery;
|
||||
snack: SnackQuery;
|
||||
submissions: SubmissionQuery;
|
||||
@@ -428,19 +430,31 @@ export type App = Project & {
|
||||
fullName: Scalars['String'];
|
||||
description: Scalars['String'];
|
||||
slug: Scalars['String'];
|
||||
updated: Scalars['DateTime'];
|
||||
published: Scalars['Boolean'];
|
||||
ownerAccount: Account;
|
||||
githubUrl?: Maybe<Scalars['String']>;
|
||||
playStoreUrl?: Maybe<Scalars['String']>;
|
||||
appStoreUrl?: Maybe<Scalars['String']>;
|
||||
icon?: Maybe<AppIcon>;
|
||||
sdkVersion: Scalars['String'];
|
||||
isDeprecated: Scalars['Boolean'];
|
||||
privacySetting: AppPrivacy;
|
||||
latestReleaseId: Scalars['ID'];
|
||||
pushSecurityEnabled: Scalars['Boolean'];
|
||||
/** Whether there have been any classic update publishes */
|
||||
published: Scalars['Boolean'];
|
||||
/** Time of last classic update publish */
|
||||
updated: Scalars['DateTime'];
|
||||
/** ID of latest classic update release */
|
||||
latestReleaseId: Scalars['ID'];
|
||||
/** Whether the latest classic update publish is using a deprecated SDK version */
|
||||
isDeprecated: Scalars['Boolean'];
|
||||
/** SDK version of the latest classic update publish, 0.0.0 otherwise */
|
||||
sdkVersion: Scalars['String'];
|
||||
/** Classic update release channel names (to be removed) */
|
||||
releaseChannels: Array<Scalars['String']>;
|
||||
/** Classic update release channel names that have at least one build */
|
||||
buildsReleaseChannels: Array<Scalars['String']>;
|
||||
/** githubUrl field from most recent classic update manifest */
|
||||
githubUrl?: Maybe<Scalars['String']>;
|
||||
/** android.playStoreUrl field from most recent classic update manifest */
|
||||
playStoreUrl?: Maybe<Scalars['String']>;
|
||||
/** ios.appStoreUrl field from most recent classic update manifest */
|
||||
appStoreUrl?: Maybe<Scalars['String']>;
|
||||
/** Info about the icon specified in the most recent classic update manifest */
|
||||
icon?: Maybe<AppIcon>;
|
||||
/** (EAS Build) Builds associated with this app */
|
||||
builds: Array<Build>;
|
||||
buildJobs: Array<BuildJob>;
|
||||
@@ -634,6 +648,12 @@ export type AppLatestReleaseForReleaseChannelArgs = {
|
||||
releaseChannel: Scalars['String'];
|
||||
};
|
||||
|
||||
export enum AppPrivacy {
|
||||
Public = 'PUBLIC',
|
||||
Unlisted = 'UNLISTED',
|
||||
Hidden = 'HIDDEN'
|
||||
}
|
||||
|
||||
export type AppIcon = {
|
||||
__typename?: 'AppIcon';
|
||||
url: Scalars['String'];
|
||||
@@ -644,12 +664,6 @@ export type AppIcon = {
|
||||
};
|
||||
|
||||
|
||||
export enum AppPrivacy {
|
||||
Public = 'PUBLIC',
|
||||
Unlisted = 'UNLISTED',
|
||||
Hidden = 'HIDDEN'
|
||||
}
|
||||
|
||||
export type BuildFilter = {
|
||||
platform?: Maybe<AppPlatform>;
|
||||
status?: Maybe<BuildStatus>;
|
||||
@@ -945,7 +959,8 @@ export enum SubmissionStatus {
|
||||
|
||||
export type AndroidSubmissionConfig = {
|
||||
__typename?: 'AndroidSubmissionConfig';
|
||||
applicationIdentifier: Scalars['String'];
|
||||
/** @deprecated applicationIdentifier is deprecated and will be auto-detected on submit */
|
||||
applicationIdentifier?: Maybe<Scalars['String']>;
|
||||
/** @deprecated archiveType is deprecated and will be null */
|
||||
archiveType?: Maybe<SubmissionAndroidArchiveType>;
|
||||
track: SubmissionAndroidTrack;
|
||||
@@ -974,7 +989,7 @@ export enum SubmissionAndroidReleaseStatus {
|
||||
export type IosSubmissionConfig = {
|
||||
__typename?: 'IosSubmissionConfig';
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
appleIdUsername: Scalars['String'];
|
||||
appleIdUsername?: Maybe<Scalars['String']>;
|
||||
ascApiKeyId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
@@ -1465,6 +1480,7 @@ export type SubscriptionDetails = {
|
||||
endedAt?: Maybe<Scalars['DateTime']>;
|
||||
trialEnd?: Maybe<Scalars['DateTime']>;
|
||||
status?: Maybe<Scalars['String']>;
|
||||
isDowngrading?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type AddonDetails = {
|
||||
@@ -1745,6 +1761,51 @@ export type ExperimentationQuery = {
|
||||
deviceExperimentationUnit: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type InvoiceQuery = {
|
||||
__typename?: 'InvoiceQuery';
|
||||
/** Preview an upgrade subscription invoice, with proration */
|
||||
previewInvoiceForSubscriptionUpdate: Invoice;
|
||||
};
|
||||
|
||||
|
||||
export type InvoiceQueryPreviewInvoiceForSubscriptionUpdateArgs = {
|
||||
accountId: Scalars['String'];
|
||||
newPlanIdentifier: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Invoice = {
|
||||
__typename?: 'Invoice';
|
||||
id: Scalars['ID'];
|
||||
/** The total amount due for the invoice, in cents */
|
||||
amountDue: Scalars['Int'];
|
||||
/** The total amount that has been paid, considering any discounts or account credit. Value is in cents. */
|
||||
amountPaid: Scalars['Int'];
|
||||
/** The total amount that needs to be paid, considering any discounts or account credit. Value is in cents. */
|
||||
amountRemaining: Scalars['Int'];
|
||||
lineItems: Array<InvoiceLineItem>;
|
||||
period: InvoicePeriod;
|
||||
startingBalance: Scalars['Int'];
|
||||
subtotal: Scalars['Int'];
|
||||
total: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type InvoiceLineItem = {
|
||||
__typename?: 'InvoiceLineItem';
|
||||
id: Scalars['ID'];
|
||||
description: Scalars['String'];
|
||||
/** Line-item amount in cents */
|
||||
amount: Scalars['Int'];
|
||||
period: InvoicePeriod;
|
||||
proration: Scalars['Boolean'];
|
||||
quantity: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type InvoicePeriod = {
|
||||
__typename?: 'InvoicePeriod';
|
||||
start: Scalars['DateTime'];
|
||||
end: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type ProjectQuery = {
|
||||
__typename?: 'ProjectQuery';
|
||||
byAccountNameAndSlug: Project;
|
||||
@@ -1904,6 +1965,7 @@ export type RootMutation = {
|
||||
iosAppBuildCredentials: IosAppBuildCredentialsMutation;
|
||||
/** Mutations that modify the credentials for an iOS app */
|
||||
iosAppCredentials: IosAppCredentialsMutation;
|
||||
keystoreGenerationUrl: KeystoreGenerationUrlMutation;
|
||||
/** Mutations that create, update, and delete Robots */
|
||||
robot: RobotMutation;
|
||||
/** Mutations that modify an EAS Submit submission */
|
||||
@@ -1998,6 +2060,10 @@ export type AccountMutation = {
|
||||
subscribeToProduct?: Maybe<Account>;
|
||||
/** Cancels the active subscription */
|
||||
cancelSubscription?: Maybe<Account>;
|
||||
/** Upgrades or downgrades the active subscription to the newPlanIdentifier, which must be one of the EAS plans (i.e., Production or Enterprise). */
|
||||
changePlan: Account;
|
||||
/** Cancel scheduled subscription change */
|
||||
cancelScheduledSubscriptionChange: Account;
|
||||
/** Requests a refund for the specified charge. Returns true if auto-refund was possible, otherwise requests a manual refund from support and returns false. */
|
||||
requestRefund?: Maybe<Scalars['Boolean']>;
|
||||
/**
|
||||
@@ -2045,6 +2111,17 @@ export type AccountMutationCancelSubscriptionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type AccountMutationChangePlanArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
newPlanIdentifier: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type AccountMutationCancelScheduledSubscriptionChangeArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type AccountMutationRequestRefundArgs = {
|
||||
accountID: Scalars['ID'];
|
||||
chargeIdentifier: Scalars['ID'];
|
||||
@@ -2823,6 +2900,18 @@ export type IosAppCredentialsInput = {
|
||||
appStoreConnectApiKeyForSubmissionsId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type KeystoreGenerationUrlMutation = {
|
||||
__typename?: 'KeystoreGenerationUrlMutation';
|
||||
/** Create a Keystore Generation URL */
|
||||
createKeystoreGenerationUrl: KeystoreGenerationUrl;
|
||||
};
|
||||
|
||||
export type KeystoreGenerationUrl = {
|
||||
__typename?: 'KeystoreGenerationUrl';
|
||||
id: Scalars['ID'];
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RobotMutation = {
|
||||
__typename?: 'RobotMutation';
|
||||
/** Create a Robot and grant it Permissions on an Account */
|
||||
@@ -2957,7 +3046,7 @@ export type AndroidSubmissionConfigInput = {
|
||||
googleServiceAccountKeyId?: Maybe<Scalars['String']>;
|
||||
googleServiceAccountKeyJson?: Maybe<Scalars['String']>;
|
||||
archiveUrl?: Maybe<Scalars['String']>;
|
||||
applicationIdentifier: Scalars['String'];
|
||||
applicationIdentifier?: Maybe<Scalars['String']>;
|
||||
track: SubmissionAndroidTrack;
|
||||
releaseStatus?: Maybe<SubmissionAndroidReleaseStatus>;
|
||||
changesNotSentForReview?: Maybe<Scalars['Boolean']>;
|
||||
@@ -3540,7 +3629,7 @@ export type GetBranchInfoQueryVariables = Exact<{
|
||||
|
||||
export type GetBranchInfoQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3550,7 +3639,7 @@ export type GetBranchInfoQuery = (
|
||||
& Pick<UpdateBranch, 'id' | 'name'>
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteUpdateBranchMutationVariables = Exact<{
|
||||
@@ -3577,7 +3666,7 @@ export type BranchesByAppQueryVariables = Exact<{
|
||||
|
||||
export type BranchesByAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3588,7 +3677,7 @@ export type BranchesByAppQuery = (
|
||||
& UpdateBranchFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetUpdateGroupAsyncQueryVariables = Exact<{
|
||||
@@ -3629,7 +3718,7 @@ export type ViewBranchQueryVariables = Exact<{
|
||||
|
||||
export type ViewBranchQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3650,7 +3739,7 @@ export type ViewBranchQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CancelBuildMutationVariables = Exact<{
|
||||
@@ -3695,7 +3784,7 @@ export type GetChannelByNameToEditQueryVariables = Exact<{
|
||||
|
||||
export type GetChannelByNameToEditQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3709,7 +3798,7 @@ export type GetChannelByNameToEditQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type UpdateChannelBranchMappingMutationVariables = Exact<{
|
||||
@@ -3738,7 +3827,7 @@ export type GetAllChannelsForAppQueryVariables = Exact<{
|
||||
|
||||
export type GetAllChannelsForAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3763,7 +3852,7 @@ export type GetAllChannelsForAppQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetChannelByNameForAppQueryVariables = Exact<{
|
||||
@@ -3774,7 +3863,7 @@ export type GetChannelByNameForAppQueryVariables = Exact<{
|
||||
|
||||
export type GetChannelByNameForAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -3799,7 +3888,7 @@ export type GetChannelByNameForAppQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type AppInfoQueryVariables = Exact<{
|
||||
@@ -3809,13 +3898,13 @@ export type AppInfoQueryVariables = Exact<{
|
||||
|
||||
export type AppInfoQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id' | 'fullName'>
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type DeleteUpdateGroupMutationVariables = Exact<{
|
||||
@@ -4056,7 +4145,7 @@ export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifi
|
||||
|
||||
export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifierQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4067,7 +4156,7 @@ export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifi
|
||||
& CommonAndroidAppCredentialsFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type GoogleServiceAccountKeyByAccountQueryVariables = Exact<{
|
||||
@@ -4453,14 +4542,14 @@ export type AppByFullNameQueryVariables = Exact<{
|
||||
|
||||
export type AppByFullNameQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id'>
|
||||
& AppFragment
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type AppStoreConnectApiKeyByAccountQueryVariables = Exact<{
|
||||
@@ -4593,7 +4682,7 @@ export type AppleDistributionCertificateByAppQueryVariables = Exact<{
|
||||
|
||||
export type AppleDistributionCertificateByAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4617,7 +4706,7 @@ export type AppleDistributionCertificateByAppQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type AppleDistributionCertificateByAccountQueryVariables = Exact<{
|
||||
@@ -4650,7 +4739,7 @@ export type AppleProvisioningProfilesByAppQueryVariables = Exact<{
|
||||
|
||||
export type AppleProvisioningProfilesByAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4682,7 +4771,7 @@ export type AppleProvisioningProfilesByAppQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type ApplePushKeyByAccountQueryVariables = Exact<{
|
||||
@@ -4753,7 +4842,7 @@ export type IosAppBuildCredentialsByAppleAppIdentiferAndDistributionQueryVariabl
|
||||
|
||||
export type IosAppBuildCredentialsByAppleAppIdentiferAndDistributionQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4768,7 +4857,7 @@ export type IosAppBuildCredentialsByAppleAppIdentiferAndDistributionQuery = (
|
||||
)> }
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type IosAppCredentialsWithBuildCredentialsByAppIdentifierIdQueryVariables = Exact<{
|
||||
@@ -4780,7 +4869,7 @@ export type IosAppCredentialsWithBuildCredentialsByAppIdentifierIdQueryVariables
|
||||
|
||||
export type IosAppCredentialsWithBuildCredentialsByAppIdentifierIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4796,7 +4885,7 @@ export type IosAppCredentialsWithBuildCredentialsByAppIdentifierIdQuery = (
|
||||
& CommonIosAppCredentialsWithoutBuildCredentialsFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CommonIosAppCredentialsWithBuildCredentialsByAppIdentifierIdQueryVariables = Exact<{
|
||||
@@ -4807,7 +4896,7 @@ export type CommonIosAppCredentialsWithBuildCredentialsByAppIdentifierIdQueryVar
|
||||
|
||||
export type CommonIosAppCredentialsWithBuildCredentialsByAppIdentifierIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -4818,7 +4907,7 @@ export type CommonIosAppCredentialsWithBuildCredentialsByAppIdentifierIdQuery =
|
||||
& CommonIosAppCredentialsFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateAppMutationVariables = Exact<{
|
||||
@@ -4939,6 +5028,20 @@ export type DeleteEnvironmentSecretMutation = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type CreateKeystoreGenerationUrlMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type CreateKeystoreGenerationUrlMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { keystoreGenerationUrl: (
|
||||
{ __typename?: 'KeystoreGenerationUrlMutation' }
|
||||
& { createKeystoreGenerationUrl: (
|
||||
{ __typename?: 'KeystoreGenerationUrl' }
|
||||
& Pick<KeystoreGenerationUrl, 'id' | 'url'>
|
||||
) }
|
||||
) }
|
||||
);
|
||||
|
||||
export type GetSignedUploadMutationVariables = Exact<{
|
||||
contentTypes: Array<Scalars['String']>;
|
||||
}>;
|
||||
@@ -5107,7 +5210,7 @@ export type GetAllBuildsForAppQueryVariables = Exact<{
|
||||
|
||||
export type GetAllBuildsForAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -5118,7 +5221,7 @@ export type GetAllBuildsForAppQuery = (
|
||||
& BuildFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type EnvironmentSecretsByAccountNameQueryVariables = Exact<{
|
||||
@@ -5149,7 +5252,7 @@ export type EnvironmentSecretsByAppIdQueryVariables = Exact<{
|
||||
|
||||
export type EnvironmentSecretsByAppIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -5160,7 +5263,7 @@ export type EnvironmentSecretsByAppIdQuery = (
|
||||
& EnvironmentSecretFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type ProjectByUsernameAndSlugQueryVariables = Exact<{
|
||||
@@ -5227,7 +5330,7 @@ export type GetAllSubmissionsForAppQueryVariables = Exact<{
|
||||
|
||||
export type GetAllSubmissionsForAppQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -5238,7 +5341,7 @@ export type GetAllSubmissionsForAppQuery = (
|
||||
& SubmissionFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
@@ -5271,7 +5374,7 @@ export type WebhooksByAppIdQueryVariables = Exact<{
|
||||
|
||||
export type WebhooksByAppIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
& { app: (
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
@@ -5282,7 +5385,7 @@ export type WebhooksByAppIdQuery = (
|
||||
& WebhookFragment
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
export type WebhookByIdQueryVariables = Exact<{
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import { CreateKeystoreGenerationUrlMutation } from '../generated';
|
||||
|
||||
export const KeystoreGenerationUrlMutation = {
|
||||
async createKeystoreGenerationUrlAsync(): Promise<string> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateKeystoreGenerationUrlMutation>(
|
||||
gql`
|
||||
mutation CreateKeystoreGenerationUrlMutation {
|
||||
keystoreGenerationUrl {
|
||||
createKeystoreGenerationUrl {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.keystoreGenerationUrl.createKeystoreGenerationUrl.url;
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
|
||||
import { withAnalyticsAsync } from '../analytics/common';
|
||||
import { Event, SubmissionEvent } from '../analytics/events';
|
||||
import {
|
||||
AndroidSubmissionConfigInput,
|
||||
IosSubmissionConfigInput,
|
||||
@@ -18,12 +20,58 @@ export interface SubmissionInput<P extends Platform> {
|
||||
: IosSubmissionConfigInput;
|
||||
buildId?: string;
|
||||
}
|
||||
export default abstract class BaseSubmitter<P extends Platform, SubmissionOptions> {
|
||||
constructor(protected ctx: SubmissionContext<P>, protected options: SubmissionOptions) {}
|
||||
|
||||
public abstract submitAsync(): Promise<SubmissionFragment>;
|
||||
interface AnalyticEvents {
|
||||
attemptEvent: Event;
|
||||
successEvent: Event;
|
||||
failureEvent: Event;
|
||||
}
|
||||
|
||||
protected async createSubmissionAsync(
|
||||
export default abstract class BaseSubmitter<
|
||||
P extends Platform,
|
||||
ResolvedSourceOptions,
|
||||
SubmissionOptions
|
||||
> {
|
||||
constructor(
|
||||
protected ctx: SubmissionContext<P>,
|
||||
protected options: SubmissionOptions,
|
||||
protected sourceOptionResolver: {
|
||||
[K in keyof ResolvedSourceOptions]: () => Promise<ResolvedSourceOptions[K]>;
|
||||
},
|
||||
protected sourceOptionAnalytics: Record<keyof ResolvedSourceOptions, AnalyticEvents>
|
||||
) {}
|
||||
|
||||
private async getSourceOptionsAsync(): Promise<ResolvedSourceOptions> {
|
||||
const resolvedSourceOptions: ResolvedSourceOptions = {} as ResolvedSourceOptions;
|
||||
// Do not perform this in parallel as some of these require user interaction
|
||||
for (const key in this.sourceOptionResolver) {
|
||||
const sourceOptionKey = key as keyof ResolvedSourceOptions;
|
||||
const sourceOptionAnalytics = this.sourceOptionAnalytics[sourceOptionKey];
|
||||
|
||||
const sourceOption = await withAnalyticsAsync<
|
||||
ResolvedSourceOptions[keyof ResolvedSourceOptions]
|
||||
>(async () => await this.sourceOptionResolver[sourceOptionKey](), {
|
||||
attemptEvent: sourceOptionAnalytics.attemptEvent,
|
||||
successEvent: sourceOptionAnalytics.successEvent,
|
||||
failureEvent: sourceOptionAnalytics.failureEvent,
|
||||
trackingCtx: this.ctx.trackingCtx,
|
||||
});
|
||||
resolvedSourceOptions[sourceOptionKey] = sourceOption;
|
||||
}
|
||||
return resolvedSourceOptions;
|
||||
}
|
||||
|
||||
public async submitAsync(): Promise<SubmissionFragment> {
|
||||
const resolvedSourceOptions = await this.getSourceOptionsAsync();
|
||||
const input = await this.createSubmissionInputAsync(resolvedSourceOptions);
|
||||
return await this.createSubmissionWithAnalyticsAsync(input);
|
||||
}
|
||||
|
||||
public abstract createSubmissionInputAsync(
|
||||
resolvedOptions: ResolvedSourceOptions
|
||||
): Promise<SubmissionInput<P>>;
|
||||
|
||||
private async createSubmissionAsync(
|
||||
submissionInput: SubmissionInput<P>
|
||||
): Promise<SubmissionFragment> {
|
||||
Log.addNewLineIfNone();
|
||||
@@ -39,6 +87,20 @@ export default abstract class BaseSubmitter<P extends Platform, SubmissionOption
|
||||
}
|
||||
}
|
||||
|
||||
private async createSubmissionWithAnalyticsAsync(
|
||||
submissionInput: SubmissionInput<P>
|
||||
): Promise<SubmissionFragment> {
|
||||
return await withAnalyticsAsync<SubmissionFragment>(
|
||||
async () => this.createSubmissionAsync(submissionInput),
|
||||
{
|
||||
attemptEvent: SubmissionEvent.SUBMIT_REQUEST_ATTEMPT,
|
||||
successEvent: SubmissionEvent.SUBMIT_REQUEST_SUCCESS,
|
||||
failureEvent: SubmissionEvent.SUBMIT_REQUEST_FAIL,
|
||||
trackingCtx: this.ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract createPlatformSubmissionAsync(
|
||||
input: SubmissionInput<P>
|
||||
): Promise<SubmissionFragment>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { SubmissionEvent } from '../../analytics/events';
|
||||
import {
|
||||
AndroidSubmissionConfigInput,
|
||||
SubmissionAndroidReleaseStatus,
|
||||
@@ -11,6 +12,7 @@ import { SubmissionMutation } from '../../graphql/mutations/SubmissionMutation';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { Archive, ArchiveSource, getArchiveAsync } from '../ArchiveSource';
|
||||
import BaseSubmitter, { SubmissionInput } from '../BaseSubmitter';
|
||||
import { SubmissionContext } from '../context';
|
||||
import {
|
||||
ArchiveSourceSummaryFields,
|
||||
formatArchiveSourceSummary,
|
||||
@@ -22,7 +24,6 @@ import {
|
||||
ServiceAccountSource,
|
||||
getServiceAccountKeyResultAsync,
|
||||
} from './ServiceAccountSource';
|
||||
|
||||
export interface AndroidSubmissionOptions
|
||||
extends Pick<
|
||||
AndroidSubmissionConfigInput,
|
||||
@@ -35,32 +36,58 @@ export interface AndroidSubmissionOptions
|
||||
}
|
||||
|
||||
interface ResolvedSourceOptions {
|
||||
androidPackage: string;
|
||||
archive: Archive;
|
||||
serviceAccountKeyResult: ServiceAccountKeyResult;
|
||||
}
|
||||
|
||||
export default class AndroidSubmitter extends BaseSubmitter<
|
||||
Platform.ANDROID,
|
||||
ResolvedSourceOptions,
|
||||
AndroidSubmissionOptions
|
||||
> {
|
||||
async submitAsync(): Promise<SubmissionFragment> {
|
||||
const resolvedSourceOptions = await this.resolveSourceOptionsAsync();
|
||||
const submissionConfig = await this.formatSubmissionConfigAsync(
|
||||
this.options,
|
||||
resolvedSourceOptions
|
||||
);
|
||||
constructor(ctx: SubmissionContext<Platform.ANDROID>, options: AndroidSubmissionOptions) {
|
||||
const sourceOptionsResolver = {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
archive: async () => await getArchiveAsync(this.options.archiveSource),
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
serviceAccountKeyResult: async () => {
|
||||
const androidPackage = await getAndroidPackageAsync(this.options.androidPackageSource);
|
||||
return await getServiceAccountKeyResultAsync(
|
||||
this.ctx,
|
||||
this.options.serviceAccountSource,
|
||||
androidPackage
|
||||
);
|
||||
},
|
||||
};
|
||||
const sourceOptionsAnalytics = {
|
||||
archive: {
|
||||
attemptEvent: SubmissionEvent.GATHER_ARCHIVE_ATTEMPT,
|
||||
successEvent: SubmissionEvent.GATHER_ARCHIVE_SUCCESS,
|
||||
failureEvent: SubmissionEvent.GATHER_ARCHIVE_FAIL,
|
||||
},
|
||||
serviceAccountKeyResult: {
|
||||
attemptEvent: SubmissionEvent.GATHER_CREDENTIALS_ATTEMPT,
|
||||
successEvent: SubmissionEvent.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: SubmissionEvent.GATHER_CREDENTIALS_FAIL,
|
||||
},
|
||||
};
|
||||
super(ctx, options, sourceOptionsResolver, sourceOptionsAnalytics);
|
||||
}
|
||||
|
||||
public async createSubmissionInputAsync(
|
||||
resolvedSourceOptions: ResolvedSourceOptions
|
||||
): Promise<SubmissionInput<Platform.ANDROID>> {
|
||||
const submissionConfig = await this.formatSubmissionConfig(this.options, resolvedSourceOptions);
|
||||
|
||||
printSummary(
|
||||
this.prepareSummaryData(this.options, resolvedSourceOptions),
|
||||
SummaryHumanReadableKeys
|
||||
);
|
||||
|
||||
return await this.createSubmissionAsync({
|
||||
return {
|
||||
projectId: this.options.projectId,
|
||||
submissionConfig,
|
||||
buildId: resolvedSourceOptions.archive.build?.id,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
protected async createPlatformSubmissionAsync({
|
||||
@@ -75,28 +102,12 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
});
|
||||
}
|
||||
|
||||
private async resolveSourceOptionsAsync(): Promise<ResolvedSourceOptions> {
|
||||
const androidPackage = await getAndroidPackageAsync(this.options.androidPackageSource);
|
||||
const archive = await getArchiveAsync(this.options.archiveSource);
|
||||
const serviceAccountKeyResult = await getServiceAccountKeyResultAsync(
|
||||
this.ctx,
|
||||
this.options.serviceAccountSource,
|
||||
androidPackage
|
||||
);
|
||||
return {
|
||||
androidPackage,
|
||||
archive,
|
||||
serviceAccountKeyResult,
|
||||
};
|
||||
}
|
||||
|
||||
private async formatSubmissionConfigAsync(
|
||||
private formatSubmissionConfig(
|
||||
options: AndroidSubmissionOptions,
|
||||
{ archive, androidPackage, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
): Promise<AndroidSubmissionConfigInput> {
|
||||
{ archive, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
): AndroidSubmissionConfigInput {
|
||||
const { track, releaseStatus, changesNotSentForReview } = options;
|
||||
return {
|
||||
applicationIdentifier: androidPackage,
|
||||
archiveUrl: archive.url,
|
||||
track,
|
||||
changesNotSentForReview,
|
||||
@@ -107,14 +118,13 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
|
||||
private prepareSummaryData(
|
||||
options: AndroidSubmissionOptions,
|
||||
{ archive, androidPackage, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
{ archive, serviceAccountKeyResult }: ResolvedSourceOptions
|
||||
): SummaryData {
|
||||
const { projectId, track, releaseStatus, changesNotSentForReview } = options;
|
||||
|
||||
// structuring order affects table rows order
|
||||
return {
|
||||
projectId,
|
||||
androidPackage,
|
||||
track,
|
||||
changesNotSentForReview: changesNotSentForReview ?? undefined,
|
||||
releaseStatus: releaseStatus ?? undefined,
|
||||
@@ -125,7 +135,6 @@ export default class AndroidSubmitter extends BaseSubmitter<
|
||||
}
|
||||
|
||||
type SummaryData = {
|
||||
androidPackage: string;
|
||||
changesNotSentForReview?: boolean;
|
||||
formattedServiceAccount: string;
|
||||
projectId: string;
|
||||
@@ -134,7 +143,6 @@ type SummaryData = {
|
||||
} & ArchiveSourceSummaryFields;
|
||||
|
||||
const SummaryHumanReadableKeys: Record<keyof SummaryData, string> = {
|
||||
androidPackage: 'Android package',
|
||||
archivePath: 'Archive path',
|
||||
archiveUrl: 'Download URL',
|
||||
changesNotSentForReview: 'Changes not sent for a review',
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { SetupGoogleServiceAccountKey } from '../../credentials/android/actions/SetupGoogleServiceAccountKey';
|
||||
import { SetUpGoogleServiceAccountKey } from '../../credentials/android/actions/SetUpGoogleServiceAccountKey';
|
||||
import { readAndValidateServiceAccountKey } from '../../credentials/android/utils/googleServiceAccountKey';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
@@ -109,7 +109,7 @@ export async function getServiceAccountFromCredentialsServiceAsync(
|
||||
projectName: ctx.projectName,
|
||||
androidApplicationIdentifier,
|
||||
};
|
||||
const setupGoogleServiceAccountKeyAction = new SetupGoogleServiceAccountKey(appLookupParams);
|
||||
const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams);
|
||||
const androidAppCredentials = await setupGoogleServiceAccountKeyAction.runAsync(
|
||||
ctx.credentialsCtx
|
||||
);
|
||||
|
||||
@@ -126,7 +126,6 @@ describe(AndroidSubmitCommand, () => {
|
||||
appId: projectId,
|
||||
config: {
|
||||
archiveUrl: 'http://expo.dev/fake.apk',
|
||||
applicationIdentifier: testProject.appJSON.expo.android?.package,
|
||||
googleServiceAccountKeyJson: fakeFiles['/google-service-account.json'],
|
||||
releaseStatus: SubmissionAndroidReleaseStatus.Draft,
|
||||
track: SubmissionAndroidTrack.Internal,
|
||||
@@ -160,7 +159,6 @@ describe(AndroidSubmitCommand, () => {
|
||||
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith({
|
||||
appId: projectId,
|
||||
config: {
|
||||
applicationIdentifier: testProject.appJSON.expo.android?.package,
|
||||
googleServiceAccountKeyJson: fakeFiles['/google-service-account.json'],
|
||||
releaseStatus: SubmissionAndroidReleaseStatus.Draft,
|
||||
track: SubmissionAndroidTrack.Internal,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { testAndroidAppCredentialsFragment } from '../../../credentials/__tests__/fixtures-android';
|
||||
import { jester as mockJester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { SetupGoogleServiceAccountKey } from '../../../credentials/android/actions/SetupGoogleServiceAccountKey';
|
||||
import { SetUpGoogleServiceAccountKey } from '../../../credentials/android/actions/SetUpGoogleServiceAccountKey';
|
||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../context';
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../project/projectUtils');
|
||||
jest.mock('../../../credentials/android/actions/SetupGoogleServiceAccountKey');
|
||||
jest.mock('../../../credentials/android/actions/SetUpGoogleServiceAccountKey');
|
||||
jest.mock('../../../user/User', () => ({
|
||||
getUserAsync: jest.fn(() => mockJester),
|
||||
}));
|
||||
@@ -52,7 +52,7 @@ beforeAll(() => {
|
||||
'/other_dir/invalid_file.txt': 'this is not even a JSON',
|
||||
});
|
||||
jest
|
||||
.spyOn(SetupGoogleServiceAccountKey.prototype, 'runAsync')
|
||||
.spyOn(SetUpGoogleServiceAccountKey.prototype, 'runAsync')
|
||||
.mockImplementation(async () => testAndroidAppCredentialsFragment);
|
||||
});
|
||||
afterAll(() => {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { SubmitProfile } from '@expo/eas-json';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { TrackingContext } from '../analytics/common';
|
||||
import { Analytics, SubmissionEvent } from '../analytics/events';
|
||||
import { CredentialsContext } from '../credentials/context';
|
||||
import { getExpoConfig } from '../project/expoConfig';
|
||||
import { getProjectAccountName } from '../project/projectUtils';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
|
||||
@@ -12,6 +16,7 @@ export interface SubmissionContext<T extends Platform> {
|
||||
accountName: string;
|
||||
archiveFlags: SubmitArchiveFlags;
|
||||
credentialsCtx: CredentialsContext;
|
||||
trackingCtx: TrackingContext;
|
||||
exp: ExpoConfig;
|
||||
nonInteractive: boolean;
|
||||
platform: T;
|
||||
@@ -45,10 +50,22 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
||||
const user = await ensureLoggedInAsync();
|
||||
const projectName = exp.slug;
|
||||
const accountName = getProjectAccountName(exp, user);
|
||||
const accountId = findAccountByName(user.accounts, accountName)?.id;
|
||||
let credentialsCtx: CredentialsContext | undefined = params.credentialsCtx;
|
||||
if (!credentialsCtx) {
|
||||
credentialsCtx = new CredentialsContext({ projectDir, user, exp, nonInteractive });
|
||||
}
|
||||
|
||||
const trackingCtx = {
|
||||
tracking_id: uuidv4(),
|
||||
platform: params.platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
account_name: accountName,
|
||||
project_id: params.projectId,
|
||||
};
|
||||
|
||||
Analytics.logEvent(SubmissionEvent.SUBMIT_COMMAND, trackingCtx);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
accountName,
|
||||
@@ -56,5 +73,6 @@ export async function createSubmissionContextAsync<T extends Platform>(params: {
|
||||
exp,
|
||||
projectName,
|
||||
user,
|
||||
trackingCtx,
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import chalk from 'chalk';
|
||||
import wrapAnsi from 'wrap-ansi';
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import getenv from 'getenv';
|
||||
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
|
||||
import UserSettings from '../../user/UserSettings';
|
||||
import { SubmissionContext } from '../context';
|
||||
export enum AppSpecificPasswordSourceType {
|
||||
userDefined,
|
||||
prompt,
|
||||
}
|
||||
|
||||
interface AppSpecificPasswordSourceBase {
|
||||
@@ -18,40 +17,62 @@ interface AppSpecificPasswordUserDefinedSource extends AppSpecificPasswordSource
|
||||
appSpecificPassword: string;
|
||||
}
|
||||
|
||||
interface AppSpecificPasswordPromptSource extends AppSpecificPasswordSourceBase {
|
||||
sourceType: AppSpecificPasswordSourceType.prompt;
|
||||
export interface AppSpecificPasswordCredentials {
|
||||
password: string;
|
||||
appleIdUsername: string;
|
||||
}
|
||||
|
||||
export type AppSpecificPasswordSource =
|
||||
| AppSpecificPasswordUserDefinedSource
|
||||
| AppSpecificPasswordPromptSource;
|
||||
export type AppSpecificPasswordSource = AppSpecificPasswordUserDefinedSource;
|
||||
|
||||
export async function getAppSpecificPasswordAsync(
|
||||
export async function getAppSpecificPasswordLocallyAsync(
|
||||
ctx: SubmissionContext<Platform.IOS>,
|
||||
source: AppSpecificPasswordSource
|
||||
): Promise<string> {
|
||||
): Promise<AppSpecificPasswordCredentials> {
|
||||
if (source.sourceType === AppSpecificPasswordSourceType.userDefined) {
|
||||
return source.appSpecificPassword;
|
||||
} else if (source.sourceType === AppSpecificPasswordSourceType.prompt) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
wrapAnsi(
|
||||
`Please enter your Apple app-specific password. You can also provide it by using the ${chalk.bold(
|
||||
'EXPO_APPLE_APP_SPECIFIC_PASSWORD'
|
||||
)} environment variable.`,
|
||||
process.stdout.columns || 80
|
||||
)
|
||||
);
|
||||
Log.log(learnMore('https://expo.fyi/apple-app-specific-password'));
|
||||
|
||||
const { appSpecificPassword } = await promptAsync({
|
||||
name: 'appSpecificPassword',
|
||||
message: 'Your Apple app-specific password:',
|
||||
type: 'password',
|
||||
validate: (val: string) => val !== '' || 'Apple app-specific password cannot be empty!',
|
||||
});
|
||||
return appSpecificPassword;
|
||||
return {
|
||||
password: source.appSpecificPassword,
|
||||
appleIdUsername: await getAppleIdUsernameAsync(ctx),
|
||||
};
|
||||
} else {
|
||||
// exhaustive -- should never happen
|
||||
throw new Error(`Unknown app specific password source type "${(source as any)?.sourceType}"`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAppleIdUsernameAsync(
|
||||
ctx: SubmissionContext<Platform.IOS>
|
||||
): Promise<string> {
|
||||
if (ctx.profile.appleId) {
|
||||
return ctx.profile.appleId;
|
||||
}
|
||||
|
||||
const envAppleId = getenv.string('EXPO_APPLE_ID', '');
|
||||
if (envAppleId) {
|
||||
return envAppleId;
|
||||
}
|
||||
|
||||
if (ctx.credentialsCtx.appStore.authCtx?.appleId) {
|
||||
return ctx.credentialsCtx.appStore.authCtx.appleId;
|
||||
}
|
||||
|
||||
// Get the email address that was last used and set it as
|
||||
// the default value for quicker authentication.
|
||||
const lastAppleId = await UserSettings.getAsync('appleId', null);
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
if (lastAppleId) {
|
||||
return lastAppleId;
|
||||
} else {
|
||||
throw new Error('Set appleId in the submit profile (eas.json).');
|
||||
}
|
||||
}
|
||||
|
||||
const { appleId } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'appleId',
|
||||
message: `Enter your Apple ID:`,
|
||||
validate: (val: string) => !!val,
|
||||
initial: lastAppleId ?? undefined,
|
||||
});
|
||||
return appleId;
|
||||
}
|
||||
@@ -31,10 +31,15 @@ type AscApiKeySummary = {
|
||||
source: 'local' | 'EAS servers';
|
||||
path?: string;
|
||||
keyId: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type AscApiKeyFromExpoServers = {
|
||||
ascApiKeyId: string;
|
||||
};
|
||||
|
||||
export type AscApiKeyResult = {
|
||||
result: MinimalAscApiKey;
|
||||
result: MinimalAscApiKey | AscApiKeyFromExpoServers;
|
||||
summary: AscApiKeySummary;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { SetUpSubmissionCredentials } from '../../credentials/ios/actions/SetUpSubmissionCredentials';
|
||||
import Log from '../../log';
|
||||
import { getBundleIdentifierAsync } from '../../project/ios/bundleIdentifier';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { SubmissionContext } from '../context';
|
||||
import {
|
||||
AppSpecificPasswordCredentials,
|
||||
getAppleIdUsernameAsync,
|
||||
} from './AppSpecificPasswordSource';
|
||||
import { AscApiKeyResult } from './AscApiKeySource';
|
||||
|
||||
/**
|
||||
* The Credentials Service will either return an ASC API Key or an App Specific Password
|
||||
* When we no longer support the App Specific Password user prompt, refactor this into the AscApiKeySource
|
||||
*/
|
||||
export const CREDENTIALS_SERVICE_SOURCE = 'CREDENTIALS_SERVICE_SOURCE';
|
||||
export type CredentialsServiceSource = typeof CREDENTIALS_SERVICE_SOURCE;
|
||||
|
||||
export async function getFromCredentialsServiceAsync(
|
||||
ctx: SubmissionContext<Platform.IOS>
|
||||
): Promise<
|
||||
{ appSpecificPassword: AppSpecificPasswordCredentials } | { ascApiKeyResult: AscApiKeyResult }
|
||||
> {
|
||||
const bundleIdentifier = await getBundleIdentifierAsync(ctx.projectDir, ctx.exp);
|
||||
Log.log(`Looking up credentials configuration for ${bundleIdentifier}...`);
|
||||
|
||||
const appLookupParams = {
|
||||
account: nullthrows(
|
||||
findAccountByName(ctx.user.accounts, ctx.accountName),
|
||||
`You do not have access to account: ${ctx.accountName}`
|
||||
),
|
||||
projectName: ctx.projectName,
|
||||
bundleIdentifier,
|
||||
};
|
||||
const setupSubmissionCredentialsAction = new SetUpSubmissionCredentials(appLookupParams);
|
||||
const ascOrAsp = await setupSubmissionCredentialsAction.runAsync(ctx.credentialsCtx);
|
||||
const isAppSpecificPassword = typeof ascOrAsp === 'string';
|
||||
if (isAppSpecificPassword) {
|
||||
return {
|
||||
appSpecificPassword: {
|
||||
password: ascOrAsp,
|
||||
appleIdUsername: await getAppleIdUsernameAsync(ctx),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const ascKeyForSubmissions = nullthrows(
|
||||
ascOrAsp.appStoreConnectApiKeyForSubmissions,
|
||||
`An EAS Submit ASC API Key could not be found for ${ascOrAsp.appleAppIdentifier.bundleIdentifier}`
|
||||
);
|
||||
const { id, keyIdentifier, name } = ascKeyForSubmissions;
|
||||
return {
|
||||
ascApiKeyResult: {
|
||||
result: {
|
||||
ascApiKeyId: id,
|
||||
},
|
||||
summary: {
|
||||
source: 'EAS servers',
|
||||
keyId: keyIdentifier,
|
||||
name: name ?? undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import wrapAnsi from 'wrap-ansi';
|
||||
import { MissingCredentialsError } from '../../credentials/errors';
|
||||
import { SubmissionFragment } from '../../graphql/generated';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import UserSettings from '../../user/UserSettings';
|
||||
import { ArchiveSource } from '../ArchiveSource';
|
||||
import { resolveArchiveSource } from '../commons';
|
||||
import { SubmissionContext } from '../context';
|
||||
@@ -18,6 +16,7 @@ import {
|
||||
AppSpecificPasswordSourceType,
|
||||
} from './AppSpecificPasswordSource';
|
||||
import { AscApiKeySource, AscApiKeySourceType } from './AscApiKeySource';
|
||||
import { CREDENTIALS_SERVICE_SOURCE, CredentialsServiceSource } from './CredentialsServiceSource';
|
||||
import IosSubmitter, { IosSubmissionOptions } from './IosSubmitter';
|
||||
|
||||
export default class IosSubmitCommand {
|
||||
@@ -30,40 +29,51 @@ export default class IosSubmitCommand {
|
||||
return await submitter.submitAsync();
|
||||
}
|
||||
|
||||
private async resolveCredentialSubmissionOptionsAsync(): Promise<
|
||||
private resolveCredentialSubmissionOptions():
|
||||
| { appSpecificPasswordSource: Result<AppSpecificPasswordSource> }
|
||||
| { ascApiKeySource: Result<AscApiKeySource> }
|
||||
> {
|
||||
// Fall back to app specific password if no ascApiKey defined
|
||||
| { credentialsServiceSource: Result<CredentialsServiceSource> } {
|
||||
const ascApiKeySource = this.resolveAscApiKeySource();
|
||||
const shouldUseAppSpecificPassword =
|
||||
const shouldSkipAscApiKeySource =
|
||||
!ascApiKeySource.ok && ascApiKeySource.enforceError() instanceof MissingCredentialsError;
|
||||
if (shouldUseAppSpecificPassword) {
|
||||
return { appSpecificPasswordSource: this.resolveAppSpecificPasswordSource() };
|
||||
} else {
|
||||
if (!shouldSkipAscApiKeySource) {
|
||||
return { ascApiKeySource };
|
||||
}
|
||||
|
||||
const appSpecificPasswordSource = this.resolveAppSpecificPasswordSource();
|
||||
const shouldSkipAppSpecificPasswordSource =
|
||||
!appSpecificPasswordSource.ok &&
|
||||
appSpecificPasswordSource.enforceError() instanceof MissingCredentialsError;
|
||||
if (!shouldSkipAppSpecificPasswordSource) {
|
||||
return { appSpecificPasswordSource: this.resolveAppSpecificPasswordSource() };
|
||||
}
|
||||
|
||||
return {
|
||||
credentialsServiceSource: result(CREDENTIALS_SERVICE_SOURCE),
|
||||
};
|
||||
}
|
||||
|
||||
private async resolveSubmissionOptionsAsync(): Promise<IosSubmissionOptions> {
|
||||
const archiveSource = this.resolveArchiveSource();
|
||||
const credentialsSource = await this.resolveCredentialSubmissionOptionsAsync();
|
||||
const credentialsSource = this.resolveCredentialSubmissionOptions();
|
||||
const maybeAppSpecificPasswordSource =
|
||||
'appSpecificPasswordSource' in credentialsSource
|
||||
? credentialsSource.appSpecificPasswordSource
|
||||
: null;
|
||||
const maybeAscApiKeySource =
|
||||
'ascApiKeySource' in credentialsSource ? credentialsSource.ascApiKeySource : null;
|
||||
const maybeCredentialsSource = null; // todo
|
||||
const maybeCredentialsServiceSource =
|
||||
'credentialsServiceSource' in credentialsSource
|
||||
? credentialsSource.credentialsServiceSource
|
||||
: null;
|
||||
const ascAppIdentifier = await this.resolveAscAppIdentifierAsync();
|
||||
const appleIdUsername = await this.resolveAppleIdUsernameAsync();
|
||||
|
||||
const errored = [
|
||||
archiveSource,
|
||||
...(maybeAppSpecificPasswordSource ? [maybeAppSpecificPasswordSource] : []),
|
||||
...(maybeAscApiKeySource ? [maybeAscApiKeySource] : []),
|
||||
...(maybeCredentialsServiceSource ? [maybeCredentialsServiceSource] : []),
|
||||
ascAppIdentifier,
|
||||
appleIdUsername,
|
||||
].filter(r => !r.ok);
|
||||
if (errored.length > 0) {
|
||||
const message = errored.map(err => err.reason?.message).join('\n');
|
||||
@@ -73,7 +83,6 @@ export default class IosSubmitCommand {
|
||||
|
||||
return {
|
||||
projectId: this.ctx.projectId,
|
||||
appleIdUsername: appleIdUsername.enforceValue(),
|
||||
ascAppIdentifier: ascAppIdentifier.enforceValue(),
|
||||
archiveSource: archiveSource.enforceValue(),
|
||||
...(maybeAppSpecificPasswordSource
|
||||
@@ -86,6 +95,11 @@ export default class IosSubmitCommand {
|
||||
ascApiKeySource: maybeAscApiKeySource.enforceValue(),
|
||||
}
|
||||
: null),
|
||||
...(maybeCredentialsServiceSource
|
||||
? {
|
||||
credentialsServiceSource: maybeCredentialsServiceSource.enforceValue(),
|
||||
}
|
||||
: null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,13 +111,13 @@ export default class IosSubmitCommand {
|
||||
sourceType: AppSpecificPasswordSourceType.userDefined,
|
||||
appSpecificPassword: envAppSpecificPassword,
|
||||
});
|
||||
} else if (this.ctx.nonInteractive) {
|
||||
return result(new Error('Set the EXPO_APPLE_APP_SPECIFIC_PASSWORD environment variable.'));
|
||||
} else {
|
||||
return result({
|
||||
sourceType: AppSpecificPasswordSourceType.prompt,
|
||||
});
|
||||
}
|
||||
|
||||
return result(
|
||||
new MissingCredentialsError(
|
||||
'The EXPO_APPLE_APP_SPECIFIC_PASSWORD environment variable must be set.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private resolveAscApiKeySource(): Result<AscApiKeySource> {
|
||||
@@ -167,40 +181,4 @@ export default class IosSubmitCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveAppleIdUsernameAsync(): Promise<Result<string>> {
|
||||
if (this.ctx.profile.appleId) {
|
||||
return result(this.ctx.profile.appleId);
|
||||
}
|
||||
|
||||
const envAppleId = getenv.string('EXPO_APPLE_ID', '');
|
||||
if (envAppleId) {
|
||||
return result(envAppleId);
|
||||
}
|
||||
|
||||
if (this.ctx.credentialsCtx.appStore.authCtx?.appleId) {
|
||||
return result(this.ctx.credentialsCtx.appStore.authCtx.appleId);
|
||||
}
|
||||
|
||||
// Get the email address that was last used and set it as
|
||||
// the default value for quicker authentication.
|
||||
const lastAppleId = await UserSettings.getAsync('appleId', null);
|
||||
|
||||
if (this.ctx.nonInteractive) {
|
||||
if (lastAppleId) {
|
||||
return result(lastAppleId);
|
||||
} else {
|
||||
return result(new Error('Set appleId in the submit profile (eas.json).'));
|
||||
}
|
||||
}
|
||||
|
||||
const { appleId } = await promptAsync({
|
||||
type: 'text',
|
||||
name: 'appleId',
|
||||
message: `Enter your Apple ID:`,
|
||||
validate: (val: string) => !!val,
|
||||
initial: lastAppleId ?? undefined,
|
||||
});
|
||||
return result(appleId);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,34 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { SubmissionEvent } from '../../analytics/events';
|
||||
import { MinimalAscApiKey } from '../../credentials/ios/credentials';
|
||||
import { IosSubmissionConfigInput, SubmissionFragment } from '../../graphql/generated';
|
||||
import { SubmissionMutation } from '../../graphql/mutations/SubmissionMutation';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { Archive, ArchiveSource, getArchiveAsync } from '../ArchiveSource';
|
||||
import BaseSubmitter, { SubmissionInput } from '../BaseSubmitter';
|
||||
import { SubmissionContext } from '../context';
|
||||
import {
|
||||
ArchiveSourceSummaryFields,
|
||||
formatArchiveSourceSummary,
|
||||
printSummary,
|
||||
} from '../utils/summary';
|
||||
import {
|
||||
AppSpecificPasswordCredentials,
|
||||
AppSpecificPasswordSource,
|
||||
getAppSpecificPasswordAsync,
|
||||
getAppSpecificPasswordLocallyAsync,
|
||||
} from './AppSpecificPasswordSource';
|
||||
import { AscApiKeyResult, AscApiKeySource, getAscApiKeyLocallyAsync } from './AscApiKeySource';
|
||||
import {
|
||||
AscApiKeyFromExpoServers,
|
||||
AscApiKeyResult,
|
||||
AscApiKeySource,
|
||||
getAscApiKeyLocallyAsync,
|
||||
} from './AscApiKeySource';
|
||||
import {
|
||||
CredentialsServiceSource,
|
||||
getFromCredentialsServiceAsync,
|
||||
} from './CredentialsServiceSource';
|
||||
|
||||
export interface IosSubmissionOptions
|
||||
extends Pick<IosSubmissionConfigInput, 'appleIdUsername' | 'ascAppIdentifier'> {
|
||||
@@ -23,32 +36,77 @@ export interface IosSubmissionOptions
|
||||
archiveSource: ArchiveSource;
|
||||
appSpecificPasswordSource?: AppSpecificPasswordSource;
|
||||
ascApiKeySource?: AscApiKeySource;
|
||||
credentialsServiceSource?: CredentialsServiceSource;
|
||||
}
|
||||
|
||||
interface ResolvedSourceOptions {
|
||||
archive: Archive;
|
||||
appSpecificPassword?: string;
|
||||
ascApiKeyResult?: AscApiKeyResult;
|
||||
credentials: {
|
||||
appSpecificPassword?: AppSpecificPasswordCredentials;
|
||||
ascApiKeyResult?: AscApiKeyResult;
|
||||
};
|
||||
}
|
||||
|
||||
export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmissionOptions> {
|
||||
async submitAsync(): Promise<SubmissionFragment> {
|
||||
const resolvedSourceOptions = await this.resolveSourceOptionsAsync();
|
||||
const submissionConfig = await this.formatSubmissionConfigAsync(
|
||||
this.options,
|
||||
resolvedSourceOptions
|
||||
);
|
||||
export default class IosSubmitter extends BaseSubmitter<
|
||||
Platform.IOS,
|
||||
ResolvedSourceOptions,
|
||||
IosSubmissionOptions
|
||||
> {
|
||||
constructor(ctx: SubmissionContext<Platform.IOS>, options: IosSubmissionOptions) {
|
||||
const sourceOptionsResolver = {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
archive: async () => await getArchiveAsync(this.options.archiveSource),
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
credentials: async () => {
|
||||
const maybeAppSpecificPassword = this.options.appSpecificPasswordSource
|
||||
? await getAppSpecificPasswordLocallyAsync(
|
||||
this.ctx,
|
||||
this.options.appSpecificPasswordSource
|
||||
)
|
||||
: null;
|
||||
const maybeAppStoreConnectApiKey = this.options.ascApiKeySource
|
||||
? await getAscApiKeyLocallyAsync(this.ctx, this.options.ascApiKeySource)
|
||||
: null;
|
||||
const maybeAscOrAspFromCredentialsService = this.options.credentialsServiceSource
|
||||
? await getFromCredentialsServiceAsync(this.ctx)
|
||||
: null;
|
||||
return {
|
||||
...(maybeAppSpecificPassword ? { appSpecificPassword: maybeAppSpecificPassword } : null),
|
||||
...(maybeAppStoreConnectApiKey ? { ascApiKeyResult: maybeAppStoreConnectApiKey } : null),
|
||||
...(maybeAscOrAspFromCredentialsService ? maybeAscOrAspFromCredentialsService : null),
|
||||
};
|
||||
},
|
||||
};
|
||||
const sourceOptionsAnalytics = {
|
||||
archive: {
|
||||
attemptEvent: SubmissionEvent.GATHER_ARCHIVE_ATTEMPT,
|
||||
successEvent: SubmissionEvent.GATHER_ARCHIVE_SUCCESS,
|
||||
failureEvent: SubmissionEvent.GATHER_ARCHIVE_FAIL,
|
||||
},
|
||||
credentials: {
|
||||
attemptEvent: SubmissionEvent.GATHER_CREDENTIALS_ATTEMPT,
|
||||
successEvent: SubmissionEvent.GATHER_CREDENTIALS_SUCCESS,
|
||||
failureEvent: SubmissionEvent.GATHER_CREDENTIALS_FAIL,
|
||||
},
|
||||
};
|
||||
super(ctx, options, sourceOptionsResolver, sourceOptionsAnalytics);
|
||||
}
|
||||
|
||||
public async createSubmissionInputAsync(
|
||||
resolvedSourceOptions: ResolvedSourceOptions
|
||||
): Promise<SubmissionInput<Platform.IOS>> {
|
||||
const submissionConfig = this.formatSubmissionConfig(this.options, resolvedSourceOptions);
|
||||
|
||||
printSummary(
|
||||
this.prepareSummaryData(this.options, resolvedSourceOptions),
|
||||
SummaryHumanReadableKeys
|
||||
);
|
||||
|
||||
return await this.createSubmissionAsync({
|
||||
return {
|
||||
projectId: this.options.projectId,
|
||||
submissionConfig,
|
||||
buildId: resolvedSourceOptions.archive.build?.id,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
protected async createPlatformSubmissionAsync({
|
||||
@@ -63,57 +121,57 @@ export default class IosSubmitter extends BaseSubmitter<Platform.IOS, IosSubmiss
|
||||
});
|
||||
}
|
||||
|
||||
private async resolveSourceOptionsAsync(): Promise<ResolvedSourceOptions> {
|
||||
const archive = await getArchiveAsync(this.options.archiveSource);
|
||||
const maybeAppSpecificPassword = this.options.appSpecificPasswordSource
|
||||
? await getAppSpecificPasswordAsync(this.options.appSpecificPasswordSource)
|
||||
: null;
|
||||
const maybeAppStoreConnectApiKey = this.options.ascApiKeySource
|
||||
? await getAscApiKeyLocallyAsync(this.ctx, this.options.ascApiKeySource)
|
||||
: null;
|
||||
return {
|
||||
archive,
|
||||
...(maybeAppSpecificPassword ? { appSpecificPassword: maybeAppSpecificPassword } : null),
|
||||
...(maybeAppStoreConnectApiKey ? { ascApiKeyResult: maybeAppStoreConnectApiKey } : null),
|
||||
};
|
||||
}
|
||||
|
||||
private async formatSubmissionConfigAsync(
|
||||
private formatSubmissionConfig(
|
||||
options: IosSubmissionOptions,
|
||||
{ archive, appSpecificPassword, ascApiKeyResult }: ResolvedSourceOptions
|
||||
): Promise<IosSubmissionConfigInput> {
|
||||
{ archive, credentials }: ResolvedSourceOptions
|
||||
): IosSubmissionConfigInput {
|
||||
const { appSpecificPassword, ascApiKeyResult } = credentials;
|
||||
const { appleIdUsername, ascAppIdentifier } = options;
|
||||
return {
|
||||
ascAppIdentifier,
|
||||
appleIdUsername,
|
||||
archiveUrl: archive.url,
|
||||
appleAppSpecificPassword: appSpecificPassword,
|
||||
...(ascApiKeyResult?.result
|
||||
? {
|
||||
ascApiKey: {
|
||||
keyP8: ascApiKeyResult?.result.keyP8,
|
||||
keyIdentifier: ascApiKeyResult?.result.keyId,
|
||||
issuerIdentifier: ascApiKeyResult?.result.issuerId,
|
||||
},
|
||||
}
|
||||
: null),
|
||||
...(appSpecificPassword ? this.formatAppSpecificPassword(appSpecificPassword) : null),
|
||||
...(ascApiKeyResult?.result ? this.formatAscApiKeyResult(ascApiKeyResult.result) : null),
|
||||
};
|
||||
}
|
||||
|
||||
private formatAppSpecificPassword(
|
||||
appSpecificPassword: AppSpecificPasswordCredentials
|
||||
): Pick<IosSubmissionConfigInput, 'appleAppSpecificPassword' | 'appleIdUsername'> {
|
||||
return {
|
||||
appleAppSpecificPassword: appSpecificPassword.password,
|
||||
appleIdUsername: appSpecificPassword.appleIdUsername,
|
||||
};
|
||||
}
|
||||
|
||||
private formatAscApiKeyResult(
|
||||
result: MinimalAscApiKey | AscApiKeyFromExpoServers
|
||||
): Pick<IosSubmissionConfigInput, 'ascApiKey'> | Pick<IosSubmissionConfigInput, 'ascApiKeyId'> {
|
||||
return 'ascApiKeyId' in result
|
||||
? { ascApiKeyId: result.ascApiKeyId }
|
||||
: {
|
||||
ascApiKey: {
|
||||
keyP8: result.keyP8,
|
||||
keyIdentifier: result.keyId,
|
||||
issuerIdentifier: result.issuerId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private prepareSummaryData(
|
||||
options: IosSubmissionOptions,
|
||||
{ archive, ascApiKeyResult }: ResolvedSourceOptions
|
||||
{ archive, credentials }: ResolvedSourceOptions
|
||||
): SummaryData {
|
||||
const { appleIdUsername, ascAppIdentifier, projectId } = options;
|
||||
const { ascApiKeyResult, appSpecificPassword } = credentials;
|
||||
const { ascAppIdentifier, projectId } = options;
|
||||
|
||||
// structuring order affects table rows order
|
||||
return {
|
||||
ascAppIdentifier,
|
||||
appleIdUsername: appleIdUsername ?? undefined,
|
||||
projectId,
|
||||
...(ascApiKeyResult
|
||||
? { formattedAscApiKey: formatServiceAccountSummary(ascApiKeyResult) }
|
||||
: null),
|
||||
...(appSpecificPassword ? { appleIdUsername: appSpecificPassword.appleIdUsername } : null),
|
||||
...(ascApiKeyResult ? { formattedAscApiKey: formatAscApiKeySummary(ascApiKeyResult) } : null),
|
||||
...formatArchiveSourceSummary(archive),
|
||||
};
|
||||
}
|
||||
@@ -133,13 +191,21 @@ const SummaryHumanReadableKeys: Record<keyof SummaryData, string> = {
|
||||
archiveUrl: 'Archive URL',
|
||||
archivePath: 'Archive Path',
|
||||
formattedBuild: 'Build',
|
||||
formattedAscApiKey: 'App Store Connect Api Key',
|
||||
formattedAscApiKey: 'App Store Connect API Key',
|
||||
};
|
||||
|
||||
function formatServiceAccountSummary({ summary }: AscApiKeyResult): string {
|
||||
const { source, path, keyId } = summary;
|
||||
function formatAscApiKeySummary({ summary }: AscApiKeyResult): string {
|
||||
const { source, path, keyId, name } = summary;
|
||||
|
||||
const fields = [
|
||||
{
|
||||
label: 'Key Name',
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
label: 'Key ID',
|
||||
value: keyId,
|
||||
},
|
||||
{
|
||||
label: 'Key Source',
|
||||
value: source,
|
||||
@@ -148,10 +214,6 @@ function formatServiceAccountSummary({ summary }: AscApiKeyResult): string {
|
||||
label: 'Key Path',
|
||||
value: path,
|
||||
},
|
||||
{
|
||||
label: 'Key ID',
|
||||
value: keyId,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
|
||||
|
||||
@@ -168,7 +168,7 @@ describe(getAscApiKeyPathAsync, () => {
|
||||
});
|
||||
|
||||
describe(getAscApiKeyLocallyAsync, () => {
|
||||
it('returns a local Asc Api Key file with a AscApiKeySourceType.path source', async () => {
|
||||
it('returns a local Asc API Key file with a AscApiKeySourceType.path source', async () => {
|
||||
const ctx = await getIosSubmissionContextAsync();
|
||||
const source: AscApiKeySource = {
|
||||
sourceType: AscApiKeySourceType.path,
|
||||
@@ -189,7 +189,7 @@ describe(getAscApiKeyLocallyAsync, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a local Asc Api Key file with a AscApiKeySourceType.prompt source', async () => {
|
||||
it('returns a local Asc API Key file with a AscApiKeySourceType.prompt source', async () => {
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({
|
||||
keyP8Path: '/asc-api-key.p8',
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { jester as mockJester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { testCommonIosAppCredentialsFragment } from '../../../credentials/__tests__/fixtures-ios';
|
||||
import { SetUpSubmissionCredentials } from '../../../credentials/ios/actions/SetUpSubmissionCredentials';
|
||||
import { createTestProject } from '../../../project/__tests__/project-utils';
|
||||
import { getBundleIdentifierAsync } from '../../../project/ios/bundleIdentifier';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { createSubmissionContextAsync } from '../../context';
|
||||
import { getFromCredentialsServiceAsync } from '../CredentialsServiceSource';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../prompts');
|
||||
jest.mock('../../../credentials/utils/promptForCredentials');
|
||||
jest.mock('../../../user/User', () => ({
|
||||
getUserAsync: jest.fn(() => mockJester),
|
||||
}));
|
||||
jest.mock('../../../user/Account', () => ({
|
||||
findAccountByName: jest.fn(() => mockJester.accounts[0]),
|
||||
}));
|
||||
jest.mock('../../../project/ios/bundleIdentifier');
|
||||
|
||||
const testProject = createTestProject(mockJester, {
|
||||
android: {
|
||||
package: 'com.expo.test.project',
|
||||
},
|
||||
});
|
||||
const mockManifest = { exp: testProject.appJSON.expo };
|
||||
jest.mock('@expo/config', () => ({
|
||||
getConfig: jest.fn(() => mockManifest),
|
||||
}));
|
||||
const projectId = uuidv4();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe(getFromCredentialsServiceAsync, () => {
|
||||
it('returns an App Specific Password from the credentialService source', async () => {
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.IOS,
|
||||
projectDir: testProject.projectRoot,
|
||||
projectId,
|
||||
archiveFlags: {
|
||||
url: 'http://expo.dev/fake.apk',
|
||||
},
|
||||
profile: {
|
||||
language: 'en-US',
|
||||
},
|
||||
nonInteractive: false,
|
||||
});
|
||||
jest
|
||||
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
||||
.mockImplementation(async _ctx => 'super secret');
|
||||
asMock(getBundleIdentifierAsync).mockImplementation(() => 'com.hello.world');
|
||||
asMock(promptAsync).mockImplementationOnce(() => ({ appleId: '[email protected]' }));
|
||||
|
||||
const result = await getFromCredentialsServiceAsync(ctx);
|
||||
expect(result).toEqual({
|
||||
appSpecificPassword: { password: 'super secret', appleIdUsername: '[email protected]' },
|
||||
});
|
||||
});
|
||||
it('returns an ASC API Key from the credentialService source', async () => {
|
||||
const ctx = await createSubmissionContextAsync({
|
||||
platform: Platform.IOS,
|
||||
projectDir: testProject.projectRoot,
|
||||
projectId,
|
||||
archiveFlags: {
|
||||
url: 'http://expo.dev/fake.apk',
|
||||
},
|
||||
profile: {
|
||||
language: 'en-US',
|
||||
},
|
||||
nonInteractive: true,
|
||||
});
|
||||
jest
|
||||
.spyOn(SetUpSubmissionCredentials.prototype, 'runAsync')
|
||||
.mockImplementation(async _ctx => testCommonIosAppCredentialsFragment);
|
||||
asMock(getBundleIdentifierAsync).mockImplementation(() => 'com.hello.world');
|
||||
|
||||
const result = await getFromCredentialsServiceAsync(ctx);
|
||||
expect(result).toEqual({
|
||||
ascApiKeyResult: {
|
||||
result: {
|
||||
ascApiKeyId: testCommonIosAppCredentialsFragment.appStoreConnectApiKeyForSubmissions?.id,
|
||||
},
|
||||
summary: {
|
||||
keyId:
|
||||
testCommonIosAppCredentialsFragment.appStoreConnectApiKeyForSubmissions?.keyIdentifier,
|
||||
name: testCommonIosAppCredentialsFragment.appStoreConnectApiKeyForSubmissions?.name,
|
||||
source: 'EAS servers',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { withAnalyticsAsync } from '../analytics/common';
|
||||
import { SubmissionEvent } from '../analytics/events';
|
||||
import { AppPlatform, SubmissionFragment, SubmissionStatus } from '../graphql/generated';
|
||||
import Log, { link } from '../log';
|
||||
import { appPlatformDisplayNames, appPlatformEmojis } from '../platform';
|
||||
@@ -13,11 +15,21 @@ import { waitForSubmissionsEndAsync } from './utils/wait';
|
||||
export async function submitAsync<T extends Platform>(
|
||||
ctx: SubmissionContext<T>
|
||||
): Promise<SubmissionFragment> {
|
||||
const command =
|
||||
ctx.platform === Platform.ANDROID
|
||||
? new AndroidSubmitCommand(ctx as SubmissionContext<Platform.ANDROID>)
|
||||
: new IosSubmitCommand(ctx as SubmissionContext<Platform.IOS>);
|
||||
return command.runAsync();
|
||||
return await withAnalyticsAsync(
|
||||
async () => {
|
||||
const command =
|
||||
ctx.platform === Platform.ANDROID
|
||||
? new AndroidSubmitCommand(ctx as SubmissionContext<Platform.ANDROID>)
|
||||
: new IosSubmitCommand(ctx as SubmissionContext<Platform.IOS>);
|
||||
return command.runAsync();
|
||||
},
|
||||
{
|
||||
attemptEvent: SubmissionEvent.SUBMIT_COMMAND_ATTEMPT,
|
||||
successEvent: SubmissionEvent.SUBMIT_COMMAND_SUCCESS,
|
||||
failureEvent: SubmissionEvent.SUBMIT_COMMAND_FAIL,
|
||||
trackingCtx: ctx.trackingCtx,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function waitToCompleteAsync(
|
||||
|
||||
@@ -16,6 +16,7 @@ enum SubmissionErrorCode {
|
||||
IOS_INVALID_SIGNATURE = 'SUBMISSION_SERVICE_IOS_INVALID_SIGNATURE',
|
||||
IOS_INCORRECT_CREDENTIALS = 'SUBMISSION_SERVICE_IOS_INVALID_CREDENTIALS',
|
||||
IOS_IPAD_INVALID_ORIENTATION = 'SUBMISSION_SERVICE_IOS_IPAD_INVALID_ORIENTATION',
|
||||
IOS_APPLE_MAINTENANCE = 'SUBMISSION_SERVICE_IOS_APPLE_MAINTENANCE',
|
||||
}
|
||||
|
||||
const SubmissionErrorMessages: Record<SubmissionErrorCode, string> = {
|
||||
@@ -62,6 +63,8 @@ const SubmissionErrorMessages: Record<SubmissionErrorCode, string> = {
|
||||
"Your app doesn't support iPad multitasking and has to require full screen.\n" +
|
||||
"If you're submitting a managed Expo project, set the `expo.ios.requireFullScreen` to true in app.json and build the project again.\n" +
|
||||
`${learnMore('https://expo.fyi/ipad-requires-fullscreen')}`,
|
||||
[SubmissionErrorCode.IOS_APPLE_MAINTENANCE]:
|
||||
'It looks like Apple servers are undergoing an unscheduled maintenance. Please try again later.',
|
||||
};
|
||||
|
||||
export function printSubmissionError(error: SubmissionError): boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import * as Analytics from '../analytics';
|
||||
import * as Analytics from '../analytics/rudderstackClient';
|
||||
import { apiClient } from '../api';
|
||||
import { graphqlClient } from '../graphql/client';
|
||||
import { CurrentUserQuery } from '../graphql/generated';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"version": "0.34.1",
|
||||
"version": "0.36.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
pushd $SCRIPTS_DIR >/dev/null 2>&1
|
||||
yarn $@
|
||||
popd >/dev/null 2>&1
|
||||
@@ -4,7 +4,8 @@
|
||||
"version": "0.31.0",
|
||||
"scripts": {
|
||||
"changelog-entry": "ts-node src/changelogEntry.ts",
|
||||
"release-changelog": "ts-node src/releaseChangelog.ts"
|
||||
"release-changelog": "ts-node src/releaseChangelog.ts",
|
||||
"update-local-plugin": "./src/updateLocalPlugin.sh"
|
||||
},
|
||||
"author": "Expo <[email protected]>",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -10,7 +10,7 @@ const MAIN_CATEGORIES = Object.values(CATEGORY_HEADERS).map(text =>
|
||||
);
|
||||
const FORMAT_RELEASE_HEADING = (version: string): markdown.Token =>
|
||||
markdown.createHeadingToken(
|
||||
`[${version}](https://github.com/expo/eas-cli/releases/tag/${version}) - ${dateFormat(
|
||||
`[${version}](https://github.com/expo/eas-cli/releases/tag/v${version}) - ${dateFormat(
|
||||
'isoDate'
|
||||
)}`,
|
||||
2
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../.. && pwd )"
|
||||
LOCAL_TS_PATH="$ROOT_DIR/packages/eas-cli/src/build/local.ts"
|
||||
|
||||
echo "Checking if eas-cli-local-build-plugin upgrade is available"
|
||||
|
||||
plugin_version_latest=$(npm info eas-cli-local-build-plugin@latest version)
|
||||
plugin_version_current=$(grep 'const PLUGIN_PACKAGE_VERSION' $LOCAL_TS_PATH | cut -d"'" -f2)
|
||||
|
||||
if [[ "$plugin_version_latest" == "$plugin_version_current" ]]; then
|
||||
echo "eas-cli-local-build-plugin is already on the latest version - $plugin_version_latest"
|
||||
else
|
||||
echo "Bumping eas-cli-local-build-plugin from $plugin_version_current to $plugin_version_latest"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s/$plugin_version_current/$plugin_version_latest/g" $LOCAL_TS_PATH
|
||||
else
|
||||
sed -i "s/$plugin_version_current/$plugin_version_latest/g" $LOCAL_TS_PATH
|
||||
fi
|
||||
git add $LOCAL_TS_PATH
|
||||
git commit -m "upgrade eas-cli-local-build-plugin to $plugin_version_latest"
|
||||
fi
|
||||
Reference in new issue
Block a user