Compare commits

...
20 Commits
Author SHA1 Message Date
Dominik Sokal 80cc849137 v0.50.0 2022-04-11 10:20:16 +02:00
Dominik SokalandBrent Vatne 2d407ea9d4 [ENG-4566] refactor status updates for multi-platform builds (#1058)
* refactor status updates for multi-platform builds

* update CHANGELOG

* Update packages/eas-cli/src/build/build.ts

Co-authored-by: Brent Vatne <[email protected]>

Co-authored-by: Brent Vatne <[email protected]>
2022-04-11 10:07:20 +02:00
Quinlan Jung 91456d1c65 [eas-cli] require userAuthCtx for methods that don't support jwt (#1055)
* [eas-cli] require userAuthCtx for methods that don't support jwt

* changelog
2022-04-08 10:49:19 -07:00
Quinlan Jung 757a0ccad7 fix changelog 2022-04-07 16:38:48 -07:00
4b7cac0392 [ENG-4419][eas-cli] use asc api token to authenticate for EAS build (#1051)
* [eas-cli] enable authentication with asc api key

* Update packages/eas-cli/src/build/createContext.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-cli/src/credentials/ios/appstore/authenticate.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-cli/src/credentials/ios/appstore/authenticate.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-cli/src/credentials/ios/appstore/authenticate.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-cli/src/credentials/ios/appstore/authenticate.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-json/src/build/types.ts

Co-authored-by: Evan Bacon <[email protected]>

* Update packages/eas-json/src/build/types.ts

Co-authored-by: Evan Bacon <[email protected]>

* pr feedback

* Update packages/eas-cli/src/credentials/ios/appstore/ensureAppExists.ts

Co-authored-by: Dominik Sokal <[email protected]>

* Update packages/eas-cli/src/credentials/ios/appstore/authenticate.ts

Co-authored-by: Dominik Sokal <[email protected]>

* pr feedback

* changelog

* return instantiation back to original state

Co-authored-by: Evan Bacon <[email protected]>
Co-authored-by: Dominik Sokal <[email protected]>
2022-04-07 15:54:43 -07:00
Will Schurman 8ff8022b3f [eas-cli] Use @expo/multipart-body-parser library (#1054) 2022-04-07 12:19:15 -07:00
Wojciech Kozyra a2acea9fe9 do not delete assigned issues 2022-04-07 16:00:25 +02:00
Wojciech KozyraandKim Brandwijk 9abdfcfdbe autoclose stale issues (#1053)
* autoclose stale issues

* Update .github/workflows/stale-issues.yml

Co-authored-by: Kim Brandwijk <[email protected]>

Co-authored-by: Kim Brandwijk <[email protected]>
2022-04-07 15:51:09 +02:00
Dominik Sokal 696f9dde13 [ENG-3864] provide queue position and estimated wait time (#1049)
* update graphql

* show queue position progress + estimated wait time

* refactor

* update CHANGELOG.md

* hide estimated wait time when build is in progress

* add priority queue info
2022-04-06 13:46:06 +02:00
Expo CI 1e73c8c96f update CHANGELOG.md 2022-04-05 10:48:52 +00:00
Wojciech Kozyra 1b02a67d88 v0.49.0 2022-04-05 12:25:49 +02:00
Wojciech Kozyra d0f158a546 upgrade eas-cli-local-build-plugin to 0.0.74 2022-04-05 12:25:02 +02:00
Evan Bacon 16a2dc681b [eas-cli] Added experimental ASC provisioning profile regeneration (#1038)
* Added experimental provisioning profile regeneration

* Update CHANGELOG.md

* use utility method
2022-04-04 14:36:05 -05:00
Wojciech Kozyra 73800252ae [eas-cli] generate credentials for iOS app extensions in managed projects (#1039)
* [eas-cli] generate credentials for iOS app extensions

* update CHANGELOG.md
2022-04-04 14:40:02 +02:00
Wojciech Kozyra e7c4ba1f54 [eas-cli] make deprecation message more generic (#1047)
* [eas-cli] make deprecation message more generic

* update CHANGELOG.md
2022-04-01 14:43:28 +02:00
Dominik Sokal 84cddaf0e1 upgrade dependencies (#1043)
* upgrade dependencies

* update CHANGELOG.md
2022-04-01 12:49:20 +02:00
Wojciech Kozyra 896f7f0385 [eas-cli] fix proxy support (#1032)
* [eas-cli] fix proxy support

* update CHANGELOG.md
2022-03-25 14:47:02 +01:00
Kim Brandwijk 7f31ea6a2d chore: update bug report issue template label (#1028) 2022-03-24 22:03:13 +01:00
Evan Bacon 3ef397f96c chore: add annotations about functions that do not support App Store Connect API (#1029)
* chore: add annotations about functions that do not support App Store Connect API

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update capabilityIdentifiers.ts
2022-03-24 12:09:37 -05:00
Expo CI 680a684a8e update CHANGELOG.md 2022-03-21 11:27:13 +00:00
44 changed files with 1750 additions and 440 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
name: "\U0001F41B Bug Report in EAS CLI"
labels: 'bug'
labels: 'needs review'
description: 'You want to report a reproducible bug or regression in EAS CLI.'
body:
- type: markdown
+1 -1
View File
@@ -1,5 +1,5 @@
name: "\U0001F64B Enhancement"
labels: 'enhancement'
labels: 'enhancement,needs review'
description: 'Want us to add something to EAS CLI?'
body:
- type: markdown
+24
View File
@@ -0,0 +1,24 @@
name: Close inactive issues
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
ascending: false
operations-per-run: 300
days-before-issue-stale: 30
days-before-issue-close: 7
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity. If there is no activity in the next 7 days, the issue will be closed."
close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale. Please open a new issue if you believe you are encountering a related problem."
days-before-pr-stale: -1
days-before-pr-close: -1
enable-statistics: true
exempt-issue-labels: "needs review"
exempt-all-assignees: true
+30 -2
View File
@@ -8,14 +8,42 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Provide queue position progress and estimated wait time. ([#1049](https://github.com/expo/eas-cli/pull/1049), [#1058](https://github.com/expo/eas-cli/pull/1058) by [@dsokal](https://github.com/dsokal))
- Enable App Store authentication for ASC Api Keys with environment variables. ([#1051](https://github.com/expo/eas-cli/pull/1051) by [@quinlanj](https://github.com/quinlanj))
### 🐛 Bug fixes
- Prompt for user/password authentication where required. ([#1055](https://github.com/expo/eas-cli/pull/1055) by [@quinlanj](https://github.com/quinlanj))
### 🧹 Chores
## [0.49.0](https://github.com/expo/eas-cli/releases/tag/v0.49.0) - 2022-04-05
### 🎉 New features
- Added experimental App Store Connect API provisioning profile regeneration. ([#1038](https://github.com/expo/eas-cli/pull/1038) by [@EvanBacon](https://github.com/EvanBacon))
- Experimental support for iOS App Extensions in managed projects. ([#1039](https://github.com/expo/eas-cli/pull/1039) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
- Fix proxy support. ([#1032](https://github.com/expo/eas-cli/pull/1032) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
- Add annotations regarding App Store Connect Token session support. ([#1029](https://github.com/expo/eas-cli/pull/1029) by [@EvanBacon](https://github.com/EvanBacon))
- Upgrade dependencies. ([#1043](https://github.com/expo/eas-cli/pull/1043) by [@dsokal](https://github.com/dsokal))
- Make default deprecation message more generic. ([#1047](https://github.com/expo/eas-cli/pull/1047) by [@wkozyra95](https://github.com/wkozyra95))
## [0.48.2](https://github.com/expo/eas-cli/releases/tag/v0.48.2) - 2022-03-21
### 🎉 New features
- Add proxy support. ([#1005](https://github.com/expo/eas-cli/pull/1005) by [@wkozyra95](https://github.com/wkozyra95))
### 🐛 Bug fixes
- Do not retry build requests. ([#1024](https://github.com/expo/eas-cli/pull/1024) by [@wkozyra95](https://github.com/wkozyra95))
### 🧹 Chores
## [0.48.1](https://github.com/expo/eas-cli/releases/tag/v0.48.1) - 2022-03-15
### 🐛 Bug fixes
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.48.2"
"version": "0.50.0"
}
+5 -5
View File
@@ -19,17 +19,17 @@
"devDependencies": {
"@types/jest": "27.4.1",
"@types/node": "16.7.8",
"eslint": "8.11.0",
"eslint": "8.12.0",
"eslint-config-universe": "10.0.0",
"eslint-plugin-async-protect": "1.1.0",
"eslint-plugin-node": "11.1.0",
"jest": "27.5.1",
"jest-watch-typeahead": "1.0.0",
"lerna": "4.0.0",
"oclif": "2.5.0",
"prettier": "2.5.1",
"ts-jest": "27.1.3",
"typescript": "4.6.2"
"oclif": "2.6.3",
"prettier": "2.6.1",
"ts-jest": "27.1.4",
"typescript": "4.6.3"
},
"volta": {
"node": "16.10.0",
+135 -42
View File
@@ -48,6 +48,7 @@ eas --help COMMAND
* [`eas build:configure`](#eas-buildconfigure)
* [`eas build:inspect`](#eas-buildinspect)
* [`eas build:list`](#eas-buildlist)
* [`eas build:submit`](#eas-buildsubmit)
* [`eas build:view [BUILD_ID]`](#eas-buildview-build_id)
* [`eas channel:create [NAME]`](#eas-channelcreate-name)
* [`eas channel:edit [NAME]`](#eas-channeledit-name)
@@ -61,6 +62,9 @@ eas --help COMMAND
* [`eas device:view [UDID]`](#eas-deviceview-udid)
* [`eas diagnostics`](#eas-diagnostics)
* [`eas help [COMMAND]`](#eas-help-command)
* [`eas init`](#eas-init)
* [`eas login`](#eas-login)
* [`eas logout`](#eas-logout)
* [`eas project:info`](#eas-projectinfo)
* [`eas project:init`](#eas-projectinit)
* [`eas secret:create`](#eas-secretcreate)
@@ -77,6 +81,7 @@ eas --help COMMAND
* [`eas webhook:list`](#eas-webhooklist)
* [`eas webhook:update`](#eas-webhookupdate)
* [`eas webhook:view ID`](#eas-webhookview-id)
* [`eas whoami`](#eas-whoami)
## `eas account:login`
@@ -93,7 +98,7 @@ ALIASES
$ eas login
```
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/account/login.ts)_
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -110,7 +115,7 @@ ALIASES
$ eas logout
```
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -127,7 +132,7 @@ ALIASES
$ eas whoami
```
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/account/view.ts)_
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -141,7 +146,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/analytics.ts)_
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -190,7 +195,7 @@ DESCRIPTION
create a branch
```
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -210,7 +215,7 @@ DESCRIPTION
delete a branch
```
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -227,7 +232,7 @@ DESCRIPTION
list all branches
```
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -246,7 +251,7 @@ DESCRIPTION
rename a branch
```
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -266,7 +271,7 @@ DESCRIPTION
view a branch
```
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -295,7 +300,7 @@ DESCRIPTION
start a build
```
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/index.ts)_
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -309,7 +314,7 @@ DESCRIPTION
cancel a build
```
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -326,7 +331,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:inspect`
@@ -361,7 +366,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -394,7 +399,35 @@ DESCRIPTION
list all builds for your project
```
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/list.ts)_
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:submit`
submit app binary to App Store and/or Play Store
```
USAGE
$ eas build:submit [-p android|ios|all] [--profile <value>] [--latest | --id <value> | --path <value> | --url
<value>] [--verbose] [--wait] [--non-interactive]
FLAGS
-p, --platform=(android|ios|all)
--id=<value> ID of the build to submit
--latest Submit the latest build for specified platform
--non-interactive Run command in non-interactive mode
--path=<value> Path to the .apk/.aab/.ipa file
--profile=<value> Name of the submit profile from eas.json. Defaults to "production" if defined in
eas.json.
--url=<value> App archive url
--verbose Always print logs from Submission Service
--[no-]wait Wait for submission to complete
DESCRIPTION
submit app binary to App Store and/or Play Store
ALIASES
$ eas build:submit
```
## `eas build:view [BUILD_ID]`
@@ -411,7 +444,7 @@ DESCRIPTION
view a build for your project
```
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/build/view.ts)_
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -431,7 +464,7 @@ DESCRIPTION
create a channel
```
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -452,7 +485,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -469,7 +502,7 @@ DESCRIPTION
list all channels
```
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:view [NAME]`
@@ -489,7 +522,7 @@ DESCRIPTION
view a channel
```
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -507,7 +540,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/config.ts)_
_See code: [src/commands/config.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -521,7 +554,7 @@ DESCRIPTION
manage credentials
```
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/credentials.ts)_
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -535,7 +568,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/device/create.ts)_
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -553,7 +586,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -570,7 +603,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/device/list.ts)_
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -584,7 +617,7 @@ DESCRIPTION
view a device for your project
```
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/device/view.ts)_
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -598,7 +631,7 @@ DESCRIPTION
display environment info
```
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -620,6 +653,51 @@ DESCRIPTION
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.12/src/commands/help.ts)_
## `eas init`
create or link an EAS project
```
USAGE
$ eas init
DESCRIPTION
create or link an EAS project
ALIASES
$ eas init
```
## `eas login`
log in with your Expo account
```
USAGE
$ eas login
DESCRIPTION
log in with your Expo account
ALIASES
$ eas login
```
## `eas logout`
log out
```
USAGE
$ eas logout
DESCRIPTION
log out
ALIASES
$ eas logout
```
## `eas project:info`
information about the current project
@@ -632,7 +710,7 @@ DESCRIPTION
information about the current project
```
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/project/info.ts)_
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -649,7 +727,7 @@ ALIASES
$ eas init
```
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/project/init.ts)_
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas secret:create`
@@ -669,7 +747,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -686,7 +764,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -700,7 +778,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit`
@@ -730,7 +808,7 @@ ALIASES
$ eas build:submit
```
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/submit.ts)_
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -759,7 +837,7 @@ DESCRIPTION
publish an update group
```
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/update/index.ts)_
_See code: [src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -776,7 +854,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -796,7 +874,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -815,7 +893,7 @@ DESCRIPTION
view the recent updates for a branch
```
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/update/list.ts)_
_See code: [src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:view GROUPID`
@@ -835,7 +913,7 @@ DESCRIPTION
update group details
```
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/update/view.ts)_
_See code: [src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -855,7 +933,7 @@ DESCRIPTION
create a webhook
```
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -872,7 +950,7 @@ DESCRIPTION
delete a webhook
```
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -889,7 +967,7 @@ DESCRIPTION
list webhooks
```
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -910,7 +988,7 @@ DESCRIPTION
update a webhook
```
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -927,5 +1005,20 @@ DESCRIPTION
view a webhook
```
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.48.2/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.50.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
show the username you are logged in as
```
USAGE
$ eas whoami
DESCRIPTION
show the username you are logged in as
ALIASES
$ eas whoami
```
<!-- commandsstop -->
+435
View File
@@ -711,6 +711,39 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "billingPeriod",
"description": null,
"args": [
{
"name": "date",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "BillingPeriod",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "buildJobs",
"description": "Build Jobs associated with this account",
@@ -1224,6 +1257,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "usageMetrics",
"description": "Account query object for querying EAS usage metrics",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "AccountUsageMetrics",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userInvitations",
"description": "Pending user invitations for this account",
@@ -2033,6 +2082,165 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AccountUsageMetric",
"description": null,
"fields": [
{
"name": "metric",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "EASServiceMetric",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "metricType",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "UsageMetricType",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timestamp",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "value",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AccountUsageMetrics",
"description": null,
"fields": [
{
"name": "metricsForServiceMetric",
"description": null,
"args": [
{
"name": "granularity",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "UsageMetricsGranularity",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "metric",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "EASServiceMetric",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timespan",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "UsageMetricsTimespan",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "AccountUsageMetric",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INTERFACE",
"name": "ActivityTimelineProjectActivity",
@@ -9901,6 +10109,65 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "BillingPeriod",
"description": null,
"fields": [
{
"name": "anchor",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "end",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "start",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Boolean",
@@ -10052,6 +10319,18 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "estimatedWaitTimeLeftSeconds",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "expirationDate",
"description": null,
@@ -10092,6 +10371,18 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "initialQueuePosition",
"description": "Queue position is 1-indexed",
"args": [],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "initiatingActor",
"description": null,
@@ -13693,6 +13984,23 @@
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "EASServiceMetric",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "MANIFEST_REQUESTS",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "EditUpdateBranchInput",
@@ -22671,6 +22979,101 @@
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "UsageMetricType",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "REQUEST",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "UsageMetricsGranularity",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "DAY",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "HOUR",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "MINUTE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "TOTAL",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "UsageMetricsTimespan",
"description": null,
"fields": null,
"inputFields": [
{
"name": "end",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "start",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "User",
@@ -23685,6 +24088,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "expires",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": null,
@@ -24045,6 +24464,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "expires",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "Email to which this invitation was sent",
+13 -13
View File
@@ -1,21 +1,22 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.48.2",
"version": "0.50.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
},
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/apple-utils": "0.0.0-alpha.28",
"@expo/apple-utils": "0.0.0-alpha.30",
"@expo/code-signing-certificates": "0.0.2",
"@expo/config": "6.0.19",
"@expo/config-plugins": "4.1.0",
"@expo/config-types": "44.0.0",
"@expo/eas-build-job": "0.2.66",
"@expo/eas-json": "0.48.0",
"@expo/eas-build-job": "0.2.68",
"@expo/eas-json": "0.50.0",
"@expo/json-file": "8.2.34",
"@expo/multipart-body-parser": "1.1.0",
"@expo/pkcs12": "0.0.7",
"@expo/plist": "0.0.17",
"@expo/plugin-warn-if-update-available": "2.1.0",
@@ -24,15 +25,15 @@
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.6.0",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "1.5.3",
"@oclif/core": "1.6.4",
"@oclif/plugin-autocomplete": "1.2.0",
"@oclif/plugin-help": "5.1.12",
"@urql/core": "2.4.3",
"@urql/exchange-retry": "0.3.2",
"chalk": "4.1.2",
"cli-progress": "3.10.0",
"cli-table3": "0.6.1",
"dateformat": "4.6.3",
"dicer": "0.3.1",
"env-paths": "2.2.0",
"envinfo": "7.8.1",
"fast-deep-equal": "3.1.3",
@@ -53,9 +54,9 @@
"log-symbols": "4.1.0",
"mime": "3.0.0",
"minimatch": "3.0.5",
"nanoid": "3.3.1",
"nanoid": "3.3.2",
"node-fetch": "2.6.7",
"node-forge": "1.2.1",
"node-forge": "1.3.1",
"nullthrows": "1.1.1",
"ora": "5.1.0",
"pkg-dir": "4.2.0",
@@ -65,7 +66,6 @@
"semver": "7.3.5",
"slash": "3.0.0",
"strip-ansi": "6.0.0",
"structured-headers": "0.4.1",
"tar": "6.1.11",
"terminal-link": "2.1.1",
"tslib": "2.3.1",
@@ -77,10 +77,10 @@
"devDependencies": {
"@graphql-codegen/cli": "2.6.2",
"@graphql-codegen/introspection": "2.1.1",
"@graphql-codegen/typescript": "2.4.7",
"@graphql-codegen/typescript-operations": "2.3.4",
"@graphql-codegen/typescript": "2.4.8",
"@graphql-codegen/typescript-operations": "2.3.5",
"@types/cli-progress": "3.9.2",
"@types/dateformat": "3.0.1",
"@types/dicer": "0.2.2",
"@types/envinfo": "7.8.1",
"@types/fs-extra": "9.0.13",
"@types/getenv": "^1.0.0",
@@ -101,7 +101,7 @@
"nock": "13.2.4",
"tempy": "0.7.0",
"ts-deepmerge": "2.0.1",
"typescript": "4.6.2"
"typescript": "4.6.3"
},
"engines": {
"node": ">=12.0.0"
+279 -129
View File
@@ -1,16 +1,29 @@
import { ArchiveSource, ArchiveSourceType, Job, Metadata, Platform } from '@expo/eas-build-job';
import { CredentialsSource } from '@expo/eas-json';
import chalk from 'chalk';
import cliProgress from 'cli-progress';
import fs from 'fs-extra';
import nullthrows from 'nullthrows';
import { withAnalyticsAsync } from '../analytics/common';
import { BuildEvent } from '../analytics/events';
import { BuildFragment, BuildStatus, UploadSessionType } from '../graphql/generated';
import { getExpoWebsiteBaseUrl } from '../api';
import {
AppPlatform,
BuildFragment,
BuildPriority,
BuildStatus,
UploadSessionType,
} from '../graphql/generated';
import { BuildResult } from '../graphql/mutations/BuildMutation';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import Log, { learnMore } from '../log';
import { ora } from '../ora';
import { requestedPlatformDisplayNames } from '../platform';
import { Ora, ora } from '../ora';
import {
appPlatformDisplayNames,
appPlatformEmojis,
requestedPlatformDisplayNames,
} from '../platform';
import { uploadAsync } from '../uploads';
import { formatBytes } from '../utils/files';
import { createProgressTracker } from '../utils/progress';
@@ -100,43 +113,43 @@ export async function prepareBuildRequestForPlatformAsync<
try {
return await sendBuildRequestAsync(builder, job, metadata);
} catch (error: any) {
if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'TURTLE_DEPRECATED_JOB_FORMAT') {
Log.error('EAS Build API has changed, please upgrade to the latest eas-cli version.');
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
) {
Log.error(
'EAS Build is down for maintenance, please try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED'
) {
Log.error(
'EAS Build free tier is temporarily disabled, please try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'
) {
Log.error(
`You have already reached the maximum number of pending ${
requestedPlatformDisplayNames[job.platform]
} builds for your account. Try again later.`
);
throw new Error('Build request failed.');
} else if (error?.graphQLErrors) {
Log.error(
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, please report the issue.'
);
}
throw error;
handleBuildRequestError(error, job.platform);
}
}
};
}
function handleBuildRequestError(error: any, platform: Platform): never {
if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'TURTLE_DEPRECATED_JOB_FORMAT') {
Log.error('EAS Build API has changed, please upgrade to the latest eas-cli version.');
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_DOWN_FOR_MAINTENANCE'
) {
Log.error(
'EAS Build is down for maintenance, please try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_FREE_TIER_DISABLED') {
Log.error(
'EAS Build free tier is temporarily disabled, please try again later. Check https://status.expo.dev/ for updates.'
);
throw new Error('Build request failed.');
} else if (
error?.graphQLErrors?.[0]?.extensions?.errorCode === 'EAS_BUILD_TOO_MANY_PENDING_BUILDS'
) {
Log.error(
`You have already reached the maximum number of pending ${requestedPlatformDisplayNames[platform]} builds for your account. Try again later.`
);
throw new Error('Build request failed.');
} else if (error?.graphQLErrors) {
Log.error(
'Build request failed. Make sure you are using the latest eas-cli version. If the problem persists, please report the issue.'
);
}
throw error;
}
async function uploadProjectAsync<TPlatform extends Platform>(
ctx: BuildContext<TPlatform>
): Promise<string> {
@@ -208,105 +221,242 @@ async function sendBuildRequestAsync<TPlatform extends Platform, Credentials, TJ
);
}
type MaybeBuildFragment = BuildFragment | null;
export async function waitForBuildEndAsync(
buildIds: string[],
{ timeoutSec = 3600, intervalSec = 30 } = {}
): Promise<(BuildFragment | null)[]> {
Log.log(
`Waiting for build${buildIds.length > 1 ? 's' : ''} to complete. You can press Ctrl+C to exit.`
);
const spinner = ora().start();
let time = new Date().getTime();
const endTime = time + timeoutSec * 1000;
while (time <= endTime) {
const builds: (BuildFragment | null)[] = await Promise.all(
buildIds.map(async buildId => {
try {
return await BuildQuery.byIdAsync(buildId, { useCache: false });
} catch (err) {
Log.debug('Failed to fetch the build status', err);
return null;
}
})
);
if (builds.length === 1) {
const [build] = builds;
if (build !== null) {
switch (build.status) {
case BuildStatus.Finished:
spinner.succeed('Build finished');
return builds;
case BuildStatus.New:
spinner.text = 'Build created';
break;
case BuildStatus.InQueue:
spinner.text = 'Build queued...';
break;
case BuildStatus.Canceled:
spinner.text = 'Build canceled';
spinner.stopAndPersist();
return builds;
case BuildStatus.InProgress:
spinner.text = 'Build in progress...';
break;
case BuildStatus.Errored:
spinner.fail('Build failed');
if (build.error) {
return builds;
} else {
throw new Error(`Standalone build failed!`);
}
default:
spinner.warn('Unknown status');
throw new Error(`Unknown build status: ${build.status} - aborting!`);
}
} else {
if (!spinner.text) {
spinner.text = 'Could not fetch the build status. Check your network connection.';
}
}
} else {
if (builds.filter(build => build?.status === BuildStatus.Finished).length === builds.length) {
spinner.succeed('All builds have finished');
return builds;
} else if (
builds.filter(build =>
build?.status
? [BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(
build.status
)
: false
).length === builds.length
) {
spinner.fail('Some of the builds were canceled or failed.');
return builds;
} else {
const newBuilds = builds.filter(build => build?.status === BuildStatus.New).length;
const inQueue = builds.filter(build => build?.status === BuildStatus.InQueue).length;
const inProgress = builds.filter(build => build?.status === BuildStatus.InProgress).length;
const errored = builds.filter(build => build?.status === BuildStatus.Errored).length;
const finished = builds.filter(build => build?.status === BuildStatus.Finished).length;
const canceled = builds.filter(build => build?.status === BuildStatus.Canceled).length;
const unknown =
builds.length - newBuilds - inQueue - inProgress - errored - finished - canceled;
spinner.text = [
newBuilds && `Builds created: ${newBuilds}`,
inQueue && `Builds in queue: ${inQueue}`,
inProgress && `Builds in progress: ${inProgress}`,
canceled && `Builds canceled: ${canceled}`,
errored && chalk.red(`Builds failed: ${errored}`),
finished && chalk.green(`Builds finished: ${finished}`),
unknown && chalk.red(`Builds with unknown status: ${unknown}`),
]
.filter(i => i)
.join('\t');
}
{ buildIds, accountName }: { buildIds: string[]; accountName: string },
{
// 2 hours (max build time limit) + 10 minutes (possible queue time )
timeoutSec = 2 * 60 * 60 + 10 * 60,
intervalSec = 10,
} = {}
): Promise<MaybeBuildFragment[]> {
const b = `build${buildIds.length > 1 ? 's' : ''}`;
Log.log(`Waiting for ${b} to complete. You can press Ctrl+C to exit.`);
const spinner = ora(`Waiting for ${b} to complete.`).start();
const endTime = new Date().getTime() + timeoutSec * 1000;
while (new Date().getTime() <= endTime) {
const builds = await getBuildsSafelyAsync(buildIds);
const { refetch } =
builds.length === 1
? await handleSingleBuildProgressAsync({ build: builds[0], accountName }, { spinner })
: await handleMultipleBuildsProgressAsync({ builds }, { spinner });
if (!refetch) {
return builds;
}
time = new Date().getTime();
await sleepAsync(intervalSec * 1000);
}
spinner.warn('Timed out');
spinner.fail('Timed out');
throw new Error(
'Timeout reached! It is taking longer than expected to finish the build, aborting...'
);
}
async function getBuildsSafelyAsync(buildIds: string[]): Promise<MaybeBuildFragment[]> {
const promises = buildIds.map(async buildId => {
try {
return await BuildQuery.byIdAsync(buildId, { useCache: false });
} catch (err) {
Log.debug('Failed to fetch the build status', err);
return null;
}
});
return await Promise.all(promises);
}
interface BuildProgressResult {
refetch: boolean;
}
let queueProgressBarStarted = false;
const queueProgressBar = new cliProgress.SingleBar(
{ format: '|{bar}| {estimatedWaitTime}' },
cliProgress.Presets.rect
);
async function handleSingleBuildProgressAsync(
{ build, accountName }: { build: MaybeBuildFragment; accountName: string },
{ spinner }: { spinner: Ora }
): Promise<BuildProgressResult> {
if (build === null) {
spinner.text = 'Could not fetch the build status. Check your network connection.';
return { refetch: true };
}
if (queueProgressBarStarted && build?.status && build.status !== BuildStatus.InQueue) {
if (build.status === BuildStatus.InProgress) {
queueProgressBar.update(queueProgressBar.getTotal(), {
estimatedWaitTime: '',
});
}
queueProgressBar.stop();
Log.newLine();
queueProgressBarStarted = false;
spinner.start('Build is about to start');
}
switch (build.status) {
case BuildStatus.Finished:
spinner.succeed('Build finished');
return { refetch: false };
case BuildStatus.New:
spinner.text = `Build is waiting to enter the queue. Check your concurrency limit at ${chalk.underline(
formatAccountSubscriptionsUrl(accountName)
)}.`;
break;
case BuildStatus.InQueue: {
spinner.text = 'Build queued...';
const progressBarPayload =
typeof build.estimatedWaitTimeLeftSeconds === 'number'
? { estimatedWaitTime: formatEstimatedWaitTime(build.estimatedWaitTimeLeftSeconds) }
: { estimatedWaitTime: '' };
if (
!queueProgressBarStarted &&
typeof build.initialQueuePosition === 'number' &&
typeof build.queuePosition === 'number'
) {
spinner.stopAndPersist();
if (build.priority !== BuildPriority.High) {
Log.newLine();
Log.log('Start builds sooner in the priority queue.');
Log.log(
`Sign up for EAS Production or Enterprise at ${chalk.underline(
formatAccountSubscriptionsUrl(accountName)
)}`
);
}
Log.newLine();
Log.log('Waiting in queue');
queueProgressBar.start(
build.initialQueuePosition + 1,
build.initialQueuePosition - build.queuePosition + 1,
progressBarPayload
);
queueProgressBarStarted = true;
}
if (typeof build.queuePosition === 'number') {
queueProgressBar.update(build.queuePosition, progressBarPayload);
}
break;
}
case BuildStatus.Canceled:
spinner.fail('Build canceled');
return { refetch: false };
case BuildStatus.InProgress:
spinner.text = 'Build in progress...';
break;
case BuildStatus.Errored:
spinner.fail('Build failed');
if (build.error) {
return { refetch: false };
} else {
throw new Error('Standalone build failed!');
}
default:
spinner.warn('Unknown status');
throw new Error(`Unknown build status: ${build.status} - aborting!`);
}
return { refetch: true };
}
const statusToDisplayName: Record<BuildStatus, string> = {
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
[BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress',
[BuildStatus.Canceled]: 'canceled',
[BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'failed',
};
const platforms = [AppPlatform.Android, AppPlatform.Ios];
async function handleMultipleBuildsProgressAsync(
{ builds: maybeBuilds }: { builds: MaybeBuildFragment[] },
{ spinner }: { spinner: Ora }
): Promise<BuildProgressResult> {
const buildCount = maybeBuilds.length;
const builds = maybeBuilds.filter<BuildFragment>(isBuildFragment);
const allFinished =
builds.filter(build => build.status === BuildStatus.Finished).length === buildCount;
const allSettled =
builds.filter(build =>
[BuildStatus.Finished, BuildStatus.Errored, BuildStatus.Canceled].includes(build.status)
).length === buildCount;
if (allSettled) {
if (allFinished) {
spinner.succeed(formatSettledBuildsText(builds));
} else {
spinner.fail(formatSettledBuildsText(builds));
}
return { refetch: false };
} else {
spinner.text = formatPendingBuildsText(builds);
return { refetch: true };
}
}
function formatSettledBuildsText(builds: BuildFragment[]): string {
return platforms
.map(platform => {
const build = nullthrows(
builds.find(build => build.platform === platform),
`Build for platform ${platform} must be defined in this context`
);
return `${appPlatformEmojis[platform]} ${
appPlatformDisplayNames[platform]
} build - status: ${chalk.bold(statusToDisplayName[build.status])}`;
})
.join('\n ');
}
function formatPendingBuildsText(builds: BuildFragment[]): string {
return platforms
.map(platform => {
const build = builds.find(build => build.platform === platform);
const status = build ? statusToDisplayName[build.status] : 'unknown';
let extraInfo = '';
if (
build?.status === BuildStatus.InQueue &&
typeof build.initialQueuePosition === 'number' &&
typeof build.queuePosition === 'number'
) {
const percent = Math.floor(
((build.initialQueuePosition - build.queuePosition + 1) /
(build.initialQueuePosition + 1)) *
100
);
const estimatedWaitTime =
typeof build.estimatedWaitTimeLeftSeconds === 'number'
? ` - ${formatEstimatedWaitTime(build.estimatedWaitTimeLeftSeconds)}`
: '';
extraInfo = ` - queue progress: ${chalk.bold(`${percent}%`)}${estimatedWaitTime}`;
}
return `${appPlatformEmojis[platform]} ${
appPlatformDisplayNames[platform]
} build - status: ${chalk.bold(status)}${extraInfo}`;
})
.join('\n ');
}
function isBuildFragment(maybeBuild: MaybeBuildFragment): maybeBuild is BuildFragment {
return maybeBuild !== null;
}
function formatEstimatedWaitTime(estimatedWaitTimeLeftSeconds: number): string {
if (estimatedWaitTimeLeftSeconds < 5 * 60) {
return 'starting soon...';
} else {
const n = Math.floor(estimatedWaitTimeLeftSeconds / (10 * 60)) + 1;
return `starting in about ${n}0 minutes...`;
}
}
function formatAccountSubscriptionsUrl(accountName: string): string {
return new URL(
`/accounts/${accountName}/settings/subscriptions`,
getExpoWebsiteBaseUrl()
).toString();
}
+1 -1
View File
@@ -6,7 +6,7 @@ import semver from 'semver';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
const PLUGIN_PACKAGE_VERSION = '0.0.67';
const PLUGIN_PACKAGE_VERSION = '0.0.74';
export interface LocalBuildOptions {
enable: boolean;
@@ -137,7 +137,11 @@ export async function runBuildAndSubmitAsync(projectDir: string, flags: BuildFla
return;
}
const builds = await waitForBuildEndAsync(startedBuilds.map(({ build }) => build.id));
const { accountName } = Object.values(buildCtxByPlatform)[0];
const builds = await waitForBuildEndAsync({
buildIds: startedBuilds.map(({ build }) => build.id),
accountName,
});
printBuildResults(builds, flags.json);
const haveAllBuildsFailedOrCanceled = builds.every(
@@ -103,10 +103,7 @@ export function printDeprecationWarnings(deprecationInfo?: EasBuildDeprecationIn
Log.warn("Changes won't affect your project config.");
Log.warn(deprecationInfo.message);
} else if (deprecationInfo.type === EasBuildDeprecationInfoType.UserFacing) {
Log.warn('This command is using API that soon will be deprecated, please update eas-cli.');
Log.warn(
'There might be some changes necessary to your project config, latest eas-cli will provide more specific error messages.'
);
Log.warn('This command is using API that soon will be deprecated.');
Log.warn(deprecationInfo.message);
} else {
Log.warn('An unexpected warning was encountered. Please report it as a bug:');
@@ -1,5 +1,5 @@
import AppStoreApi from '../ios/appstore/AppStoreApi';
import { AuthCtx } from '../ios/appstore/authenticate';
import { AuthCtx } from '../ios/appstore/authenticateTypes';
export const testAuthCtx: AuthCtx = {
appleId: 'test-apple-id',
@@ -10,6 +10,7 @@ import { Actor, getActorDisplayName } from '../user/User';
import * as AndroidGraphqlClient from './android/api/GraphqlClient';
import * as IosGraphqlClient from './ios/api/GraphqlClient';
import AppStoreApi from './ios/appstore/AppStoreApi';
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
export class CredentialsContext {
public readonly android = AndroidGraphqlClient;
@@ -99,6 +100,11 @@ export class CredentialsContext {
return;
}
if (this.appStore.defaultAuthenticationMode === AuthenticationMode.API_KEY) {
await this.appStore.ensureAuthenticatedAsync();
return;
}
Log.log(
chalk.green(
'If you provide your Apple account credentials we will be able to generate all necessary build credentials and fully validate them.'
@@ -94,7 +94,6 @@ 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),
@@ -12,7 +12,7 @@ import { MissingCredentialsNonInteractiveError } from '../../errors';
import { AppLookupParams } from '../api/GraphqlClient';
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
import { AuthCtx } from '../appstore/authenticate';
import { AuthCtx } from '../appstore/authenticateTypes';
export class ConfigureProvisioningProfile {
constructor(
@@ -84,7 +84,7 @@ export class ConfigureProvisioningProfile {
certP12: certificateP12,
certPassword: certificatePassword,
distCertSerialNumber: serialNumber,
teamId: authCtx.appleId,
teamId: authCtx.team.id,
}
);
@@ -9,7 +9,7 @@ import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
import { AppLookupParams } from '../api/GraphqlClient';
import { AppleProvisioningProfileMutationResult } from '../api/graphql/mutations/AppleProvisioningProfileMutation';
import { ProvisioningProfile } from '../appstore/Credentials.types';
import { AuthCtx } from '../appstore/authenticate';
import { AuthCtx } from '../appstore/authenticateTypes';
import { provisioningProfileSchema } from '../credentials';
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
import { generateProvisioningProfileAsync } from './ProvisioningProfileUtils';
@@ -68,7 +68,7 @@ export class CreateProvisioningProfile {
certP12: this.distributionCertificate.certificateP12,
certPassword: this.distributionCertificate.certificatePassword,
distCertSerialNumber: this.distributionCertificate.serialNumber,
teamId: this.distributionCertificate.appleTeam?.appleTeamIdentifier ?? appleAuthCtx.appleId,
teamId: this.distributionCertificate.appleTeam?.appleTeamIdentifier ?? appleAuthCtx.team.id,
});
}
}
@@ -1,3 +1,4 @@
import Log from '../../../log';
import {
AscApiKey,
AscApiKeyInfo,
@@ -14,7 +15,13 @@ import {
listAscApiKeysAsync,
revokeAscApiKeyAsync,
} from './ascApiKey';
import { AuthCtx, Options as AuthenticateOptions, authenticateAsync } from './authenticate';
import {
Options as AuthenticateOptions,
assertUserAuthCtx,
authenticateAsync,
isUserAuthCtx,
} from './authenticate';
import { AuthCtx, AuthenticationMode, UserAuthCtx } from './authenticateTypes';
import {
createDistributionCertificateAsync,
listDistributionCertificatesAsync,
@@ -34,13 +41,36 @@ import {
} from './provisioningProfile';
import { createOrReuseAdhocProvisioningProfileAsync } from './provisioningProfileAdhoc';
import { createPushKeyAsync, listPushKeysAsync, revokePushKeyAsync } from './pushKey';
import { hasAscEnvVars } from './resolveCredentials';
export default class AppStoreApi {
public authCtx?: AuthCtx;
public defaultAuthenticationMode: AuthenticationMode;
constructor() {
this.defaultAuthenticationMode = hasAscEnvVars()
? AuthenticationMode.API_KEY
: AuthenticationMode.USER;
}
public async ensureUserAuthenticatedAsync(options?: AuthenticateOptions): Promise<UserAuthCtx> {
if (this.authCtx && !isUserAuthCtx(this.authCtx)) {
// already authenticated, but with the wrong type
Log.log(`Only user authentication is supported. Reauthenticating as user...`);
this.authCtx = undefined;
}
const updatedAuthCtx = await this.ensureAuthenticatedAsync({
...options,
mode: AuthenticationMode.USER,
});
return assertUserAuthCtx(updatedAuthCtx);
}
public async ensureAuthenticatedAsync(options?: AuthenticateOptions): Promise<AuthCtx> {
if (!this.authCtx) {
this.authCtx = await authenticateAsync(options);
const mode = options?.mode ?? this.defaultAuthenticationMode;
this.authCtx = await authenticateAsync({ mode, ...options });
}
return this.authCtx;
}
@@ -69,18 +99,18 @@ export default class AppStoreApi {
}
public async listPushKeysAsync(): Promise<PushKeyStoreInfo[]> {
const ctx = await this.ensureAuthenticatedAsync();
return await listPushKeysAsync(ctx);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await listPushKeysAsync(userCtx);
}
public async createPushKeyAsync(name?: string): Promise<PushKey> {
const ctx = await this.ensureAuthenticatedAsync();
return await createPushKeyAsync(ctx, name);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await createPushKeyAsync(userCtx, name);
}
public async revokePushKeyAsync(ids: string[]): Promise<void> {
const ctx = await this.ensureAuthenticatedAsync();
return await revokePushKeyAsync(ctx, ids);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await revokePushKeyAsync(userCtx, ids);
}
public async useExistingProvisioningProfileAsync(
@@ -144,22 +174,22 @@ export default class AppStoreApi {
}
public async listAscApiKeysAsync(): Promise<AscApiKeyInfo[]> {
const ctx = await this.ensureAuthenticatedAsync();
return await listAscApiKeysAsync(ctx);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await listAscApiKeysAsync(userCtx);
}
public async getAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo | null> {
const ctx = await this.ensureAuthenticatedAsync();
return await getAscApiKeyAsync(ctx, keyId);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await getAscApiKeyAsync(userCtx, keyId);
}
public async createAscApiKeyAsync({ nickname }: { nickname: string }): Promise<AscApiKey> {
const ctx = await this.ensureAuthenticatedAsync();
return await createAscApiKeyAsync(ctx, { nickname });
const userCtx = await this.ensureUserAuthenticatedAsync();
return await createAscApiKeyAsync(userCtx, { nickname });
}
public async revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
const ctx = await this.ensureAuthenticatedAsync();
return await revokeAscApiKeyAsync(ctx, keyId);
const userCtx = await this.ensureUserAuthenticatedAsync();
return await revokeAscApiKeyAsync(userCtx, keyId);
}
}
@@ -0,0 +1,92 @@
import { vol } from 'memfs';
import { asMock } from '../../../../__tests__/utils';
import { promptAsync } from '../../../../prompts';
import { AppleTeamType } from '../authenticateTypes';
import { resolveAppleTeamAsync, resolveAscApiKeyAsync } from '../resolveCredentials';
jest.mock('../../../../prompts');
jest.mock('fs');
const testAscApiKey = {
keyP8: 'test-key-p8',
keyId: 'test-key-id',
issuerId: 'test-issuer-id',
};
beforeEach(() => {
vol.reset();
process.env = {};
asMock(promptAsync).mockReset();
});
describe(resolveAscApiKeyAsync, () => {
it(`uses option overrides over environment variables`, async () => {
process.env.EXPO_ASC_API_KEY_PATH = 'not supposed to be here';
process.env.EXPO_ASC_KEY_ID = 'not supposed to be here';
process.env.EXPO_ASC_ISSUER_ID = 'not supposed to be here';
const ascApiKey = await resolveAscApiKeyAsync(testAscApiKey);
expect(ascApiKey).toMatchObject(testAscApiKey);
});
it(`uses environment variables if no option overrides are provided`, async () => {
vol.fromJSON({
'/test-asc-key.p8': testAscApiKey.keyP8,
});
process.env.EXPO_ASC_API_KEY_PATH = '/test-asc-key.p8';
process.env.EXPO_ASC_KEY_ID = testAscApiKey.keyId;
process.env.EXPO_ASC_ISSUER_ID = testAscApiKey.issuerId;
const ascApiKey = await resolveAscApiKeyAsync();
expect(ascApiKey).toMatchObject(testAscApiKey);
});
it(`prompts the user if it can't find anything else`, async () => {
vol.fromJSON({
'/test-asc-key.p8': testAscApiKey.keyP8,
});
asMock(promptAsync).mockImplementation(() => ({
ascApiKeyPath: '/test-asc-key.p8',
ascIssuerId: testAscApiKey.issuerId,
ascApiKeyId: testAscApiKey.keyId,
}));
const ascApiKey = await resolveAscApiKeyAsync();
expect(ascApiKey).toMatchObject(testAscApiKey);
});
});
const testTeam = {
teamId: 'test-id',
teamName: 'test-name',
teamType: AppleTeamType.IN_HOUSE,
};
describe(resolveAppleTeamAsync, () => {
it(`uses option overrides over environment variables`, async () => {
process.env.EXPO_APPLE_TEAM_ID = 'not supposed to be here';
process.env.EXPO_APPLE_TEAM_TYPE = 'not supposed to be here';
const team = await resolveAppleTeamAsync(testTeam);
expect(team).toMatchObject({ id: testTeam.teamId, name: testTeam.teamName, inHouse: true });
});
it(`uses environment variables if no option overrides are provided`, async () => {
process.env.EXPO_APPLE_TEAM_ID = testTeam.teamId;
process.env.EXPO_APPLE_TEAM_TYPE = testTeam.teamType;
const team = await resolveAppleTeamAsync();
expect(team).toMatchObject({
id: testTeam.teamId,
name: undefined,
inHouse: true,
});
});
it(`prompts the user if it can't find anything else`, async () => {
asMock(promptAsync).mockImplementation(() => ({
appleTeamId: testTeam.teamId,
appleTeamType: testTeam.teamType,
}));
const team = await resolveAppleTeamAsync();
expect(team).toMatchObject({
id: testTeam.teamId,
name: undefined,
inHouse: true,
});
});
});
@@ -3,31 +3,40 @@ import { ApiKey, ApiKeyProps, ApiKeyType, UserRole } from '@expo/apple-utils';
import Log from '../../../log';
import { ora } from '../../../ora';
import { AscApiKey, AscApiKeyInfo } from './Credentials.types';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext } from './authenticate';
import { AuthCtx, UserAuthCtx } from './authenticateTypes';
export async function listAscApiKeysAsync(authCtx: AuthCtx): Promise<AscApiKeyInfo[]> {
/**
* List App Store Connect API Keys.
* **Does not support App Store Connect API (CI).**
*/
export async function listAscApiKeysAsync(userAuthCtx: UserAuthCtx): Promise<AscApiKeyInfo[]> {
const spinner = ora(`Fetching App Store Connect API Keys.`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const keys = await ApiKey.getAsync(context);
spinner.succeed(`Fetched App Store Connect API Keys.`);
return keys.map(key => getAscApiKeyInfo(key, authCtx));
return keys.map(key => getAscApiKeyInfo(key, userAuthCtx));
} catch (error) {
spinner.fail(`Failed to fetch App Store Connect API Keys.`);
throw error;
}
}
/**
* Get an App Store Connect API Key.
* **Does not support App Store Connect API (CI).**
*/
export async function getAscApiKeyAsync(
authCtx: AuthCtx,
userAuthCtx: UserAuthCtx,
keyId: string
): Promise<AscApiKeyInfo | null> {
const spinner = ora(`Fetching App Store Connect API Key.`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
spinner.succeed(`Fetched App Store Connect API Key (ID: ${keyId}).`);
return getAscApiKeyInfo(apiKey, authCtx);
return getAscApiKeyInfo(apiKey, userAuthCtx);
} catch (error: any) {
const message = error?.message ?? '';
if (message.includes("There is no resource of type 'apiKeys' with id")) {
@@ -40,8 +49,12 @@ export async function getAscApiKeyAsync(
}
}
/**
* Create an App Store Connect API Key.
* **Does not support App Store Connect API (CI).**
*/
export async function createAscApiKeyAsync(
authCtx: AuthCtx,
userAuthCtx: UserAuthCtx,
{
nickname,
allAppsVisible,
@@ -51,7 +64,7 @@ export async function createAscApiKeyAsync(
): Promise<AscApiKey> {
const spinner = ora(`Creating App Store Connect API Key.`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const key = await ApiKey.createAsync(context, {
nickname: nickname ?? `[expo] ${new Date().getTime()}`,
allAppsVisible: allAppsVisible ?? true,
@@ -76,7 +89,7 @@ export async function createAscApiKeyAsync(
const fullKey = await ApiKey.infoAsync(context, { id: key.id });
spinner.succeed(`Created App Store Connect API Key.`);
return {
...getAscApiKeyInfo(fullKey, authCtx),
...getAscApiKeyInfo(fullKey, userAuthCtx),
keyP8,
};
} catch (err: any) {
@@ -85,17 +98,21 @@ export async function createAscApiKeyAsync(
}
}
/**
* Revoke an App Store Connect API Key.
* **Does not support App Store Connect API (CI).**
*/
export async function revokeAscApiKeyAsync(
authCtx: AuthCtx,
userAuthCtx: UserAuthCtx,
keyId: string
): Promise<AscApiKeyInfo> {
const spinner = ora(`Revoking App Store Connect API Key.`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const apiKey = await ApiKey.infoAsync(context, { id: keyId });
const revokedKey = await apiKey.revokeAsync();
spinner.succeed(`Revoked App Store Connect API Key.`);
return getAscApiKeyInfo(revokedKey, authCtx);
return getAscApiKeyInfo(revokedKey, userAuthCtx);
} catch (error) {
Log.error(error);
spinner.fail(`Failed to revoke App Store Connect API Key.`);
@@ -4,16 +4,28 @@ import {
RequestContext,
Session,
Teams,
Token,
} from '@expo/apple-utils';
import assert from 'assert';
import chalk from 'chalk';
import Log from '../../../log';
import { toggleConfirmAsync } from '../../../prompts';
import { MinimalAscApiKey } from '../credentials';
import {
ApiKeyAuthCtx,
AppleTeamType,
AuthCtx,
AuthenticationMode,
Team,
UserAuthCtx,
} from './authenticateTypes';
import {
deletePasswordAsync,
promptPasswordAsync,
resolveCredentialsAsync,
resolveAppleTeamAsync,
resolveAscApiKeyAsync,
resolveUserCredentialsAsync,
} from './resolveCredentials';
const APPLE_IN_HOUSE_TEAM_TYPE = 'in-house';
@@ -21,31 +33,27 @@ const APPLE_IN_HOUSE_TEAM_TYPE = 'in-house';
export type Options = {
appleId?: string;
teamId?: string;
teamName?: string;
teamType?: AppleTeamType;
ascApiKey?: MinimalAscApiKey;
/**
* Can be used to restore the Apple auth state via apple-utils.
*/
cookies?: Session.AuthState['cookies'];
/** Indicates how Apple network requests will be made. */
mode?: AuthenticationMode;
};
export type Team = {
id: string;
name: string;
inHouse?: boolean;
};
export function isUserAuthCtx(authCtx: AuthCtx | undefined): authCtx is UserAuthCtx {
return !!authCtx && typeof (authCtx as UserAuthCtx).appleId === 'string';
}
export type AuthCtx = {
appleId: string;
appleIdPassword?: string;
team: Team;
/**
* Defined when using Fastlane
*/
fastlaneSession?: string;
/**
* Can be used to restore the Apple auth state via apple-utils.
*/
authState?: Session.AuthState;
};
export function assertUserAuthCtx(authCtx: AuthCtx | undefined): UserAuthCtx {
if (isUserAuthCtx(authCtx)) {
return authCtx;
}
throw new Error('Expected user authentication context (login/password).');
}
export function getRequestContext(authCtx: AuthCtx): RequestContext {
assert(authCtx.authState?.context, 'Apple request context must be defined');
@@ -67,7 +75,7 @@ async function loginAsync(
}
// Resolve the user credentials, optimizing for password-less login.
const { username, password } = await resolveCredentialsAsync(userCredentials);
const { username, password } = await resolveUserCredentialsAsync(userCredentials);
assert(username);
// Clear data
@@ -144,6 +152,35 @@ async function loginWithUserCredentialsAsync({
}
export async function authenticateAsync(options: Options = {}): Promise<AuthCtx> {
if (options.mode === AuthenticationMode.API_KEY) {
return await authenticateWithApiKeyAsync(options);
} else {
return await authenticateAsUserAsync(options);
}
}
async function authenticateWithApiKeyAsync(options: Options = {}): Promise<ApiKeyAuthCtx> {
// Resolve the user credentials, optimizing for password-less login.
const ascApiKey = await resolveAscApiKeyAsync(options.ascApiKey);
const team = await resolveAppleTeamAsync(options);
const jwtDurationSeconds = 1200; // 20 minutes
return {
team,
authState: {
context: {
token: new Token({
key: ascApiKey.keyP8,
issuerId: ascApiKey.issuerId,
keyId: ascApiKey.keyId,
duration: jwtDurationSeconds,
}),
},
},
ascApiKey,
};
}
async function authenticateAsUserAsync(options: Options = {}): Promise<AuthCtx> {
// help keep apple login visually apart from the other operations.
Log.addNewLineIfNone();
@@ -0,0 +1,48 @@
import { Session } from '@expo/apple-utils';
import { MinimalAscApiKey } from '../credentials';
export enum AuthenticationMode {
/** App Store API requests will be made using the official API via an API key, used for CI environments where 2FA cannot be performed. */
API_KEY,
/** Uses cookies based authentication and the unofficial App Store web API, this provides more functionality than the official API but cannot be reliably used in CI because it requires 2FA. */
USER,
}
export enum AppleTeamType {
IN_HOUSE = 'IN_HOUSE',
COMPANY_OR_ORGANIZATION = 'COMPANY_OR_ORGANIZATION',
INDIVIDUAL = 'INDIVIDUAL',
}
export type Team = {
id: string;
/** Name of the development team, this is undefined when ASC API keys are used instead of cookies for authentication. */
name?: string;
inHouse?: boolean;
};
export type UserAuthCtx = {
appleId: string;
appleIdPassword?: string;
team: Team;
/**
* Defined when using Fastlane
*/
fastlaneSession?: string;
/**
* Can be used to restore the Apple auth state via apple-utils.
*/
authState?: Session.AuthState;
};
export type ApiKeyAuthCtx = {
ascApiKey: MinimalAscApiKey;
team: Team;
/**
* Can be used to restore the Apple auth state via apple-utils.
*/
authState?: Partial<Session.AuthState>;
};
export type AuthCtx = UserAuthCtx | ApiKeyAuthCtx;
@@ -2,6 +2,7 @@ import { BundleId, CapabilityTypeOption } from '@expo/apple-utils';
import { JSONObject } from '@expo/json-file';
import Log from '../../../log';
import { isAppStoreConnectTokenOnlyContext } from '../utils/authType';
import {
CapabilityMapping,
EXPO_NO_CAPABILITY_SYNC,
@@ -15,6 +16,8 @@ type UpdateCapabilityRequest = Parameters<BundleId['updateBundleIdCapabilityAsyn
* If a capability identifier is missing, then attempt to create it.
* Link all of the capability identifiers at the same time after parsing the entitlements file.
*
* **Does not support App Store Connect API (CI).**
*
* @param bundleId Bundle identifier object.
* @param entitlements JSON representation of the iOS entitlements plist
*
@@ -28,6 +31,15 @@ export async function syncCapabilityIdentifiersForEntitlementsAsync(
return { created: [], linked: [] };
}
// App Store Connect token authentication is not currently supported,
// the team ID is required to create capability identifiers.
if (isAppStoreConnectTokenOnlyContext(bundleId.context)) {
Log.warn(
`Skipping capability identifier syncing because the current Apple authentication session is not using Cookies (username/password).`
);
return { created: [], linked: [] };
}
const createdIds: string[] = [];
const linkedIds: string[] = [];
const CapabilityIdMapping = CapabilityMapping.filter(capability => capability.capabilityIdModel);
@@ -5,6 +5,9 @@ import Log from '../../../log';
import type { Ora } from '../../../ora';
import { convertHTMLToASCII } from '../utils/convertHTMLToASCII';
/**
* **Does not support App Store Connect API (CI).**
*/
async function getContractStatusAsync(
context: RequestContext
): Promise<ITCAgreements.ITCContractStatus | null> {
@@ -17,6 +20,9 @@ async function getContractStatusAsync(
}
}
/**
* **Does not support App Store Connect API (CI).**
*/
async function getContractMessagesAsync(
context: RequestContext
): Promise<ITCAgreements.ITCContractMessage[] | null> {
@@ -28,6 +34,9 @@ async function getContractMessagesAsync(
}
}
/**
* **Does not support App Store Connect API (CI).**
*/
async function getRequiredContractMessagesAsync(
context: RequestContext
): Promise<{ messages: ITCAgreements.ITCContractMessage[]; isFatal: boolean }> {
@@ -77,6 +86,9 @@ export function formatContractMessage(message: ITCAgreements.ITCContractMessage)
});
}
/**
* **Does not support App Store Connect API (CI).**
*/
export async function assertContractMessagesAsync(
context: RequestContext,
spinner?: Ora
@@ -7,7 +7,8 @@ import {
import { ora } from '../../../ora';
import { DistributionCertificate, DistributionCertificateStoreInfo } from './Credentials.types';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext } from './authenticate';
import { AuthCtx } from './authenticateTypes';
export class AppleTooManyCertsError extends Error {}
@@ -4,7 +4,8 @@ import chalk from 'chalk';
import Log from '../../../log';
import { ora } from '../../../ora';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext, isUserAuthCtx } from './authenticate';
import { AuthCtx, UserAuthCtx } from './authenticateTypes';
import { syncCapabilitiesForEntitlementsAsync } from './bundleIdCapabilities';
import { syncCapabilityIdentifiersForEntitlementsAsync } from './capabilityIdentifiers';
import { assertContractMessagesAsync } from './contractMessages';
@@ -72,7 +73,13 @@ export async function ensureBundleIdExistsWithNameAsync(
// Unable to process request - PLA Update available - You currently don't have access to this membership resource. To resolve this issue, agree to the latest Program License Agreement in your developer account.
err.message.match(/agree/)
) {
await assertContractMessagesAsync(context);
if (isUserAuthCtx(authCtx)) {
await assertContractMessagesAsync(context);
} else {
Log.warn(
`You currently don't have access to this membership resource. To resolve this issue, agree to the latest Program License Agreement in your developer account.`
);
}
}
}
@@ -150,7 +157,7 @@ export async function syncCapabilityIdentifiersAsync(
}
export async function ensureAppExistsAsync(
authCtx: AuthCtx,
userAuthCtx: UserAuthCtx,
{
name,
language,
@@ -165,7 +172,7 @@ export async function ensureAppExistsAsync(
sku?: string;
}
): Promise<App> {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const spinner = ora(`Linking to App Store Connect ${chalk.dim(bundleIdentifier)}`).start();
let app = await App.findAsync(context, { bundleId: bundleIdentifier });
@@ -176,7 +183,9 @@ export async function ensureAppExistsAsync(
try {
// Assert contract errors when the user needs to create an app.
await assertContractMessagesAsync(context, spinner);
/**
* **Does not support App Store Connect API (CI).**
*/
app = await App.createAsync(context, {
bundleId: bundleIdentifier,
name,
@@ -186,9 +195,8 @@ export async function ensureAppExistsAsync(
});
} catch (error: any) {
if (error.message.match(/An App ID with Identifier '(.*)' is not available/)) {
const providerName = authCtx.authState?.session.provider.name;
throw new Error(
`\nThe bundle identifier "${bundleIdentifier}" is not available to provider "${providerName}". Please change it in your app config and try again.\n`
`\nThe bundle identifier "${bundleIdentifier}" is not available to provider "${userAuthCtx.authState?.session.provider.name}. Please change it in your app config and try again.\n`
);
}
@@ -1,13 +1,15 @@
import { Profile, ProfileType, RequestContext } from '@expo/apple-utils';
import { ora } from '../../../ora';
import { isAppStoreConnectTokenOnlyContext } from '../utils/authType';
import { findP12CertSerialNumber } from '../utils/p12Certificate';
import {
DistributionCertificate,
ProvisioningProfile,
ProvisioningProfileStoreInfo,
} from './Credentials.types';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext } from './authenticate';
import { AuthCtx } from './authenticateTypes';
import { getBundleIdForIdentifierAsync, getProfilesForBundleIdAsync } from './bundleId';
import { getCertificateBySerialNumberAsync, transformCertificate } from './distributionCertificate';
@@ -70,6 +72,12 @@ async function addCertificateToProfileAsync(
// Assign the new certificate
profile.attributes.certificates = [cert];
// Experimentally regenerate the provisioning profile using App Store Connect API.
if (isAppStoreConnectTokenOnlyContext(profile.context)) {
return await profile.regenerateManuallyAsync();
}
// This method does not support App Store Connect API.
return await profile.regenerateAsync();
}
@@ -1,8 +1,10 @@
import { Device, Profile, ProfileState, ProfileType, RequestContext } from '@expo/apple-utils';
import { ora } from '../../../ora';
import { isAppStoreConnectTokenOnlyContext } from '../utils/authType';
import { ProvisioningProfile } from './Credentials.types';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext } from './authenticate';
import { AuthCtx } from './authenticateTypes';
import { getBundleIdForIdentifierAsync, getProfilesForBundleIdAsync } from './bundleId';
import { getDistributionCertificateAsync } from './distributionCertificate';
@@ -89,7 +91,15 @@ async function findProfileByBundleIdAsync(
}
const profile = expoProfiles.sort(sortByExpiration)[expoProfiles.length - 1];
profile.attributes.certificates = [distributionCertificate];
return { profile: await profile.regenerateAsync(), didUpdate: true };
return {
profile: isAppStoreConnectTokenOnlyContext(profile.context)
? // Experimentally regenerate the provisioning profile using App Store Connect API.
await profile.regenerateManuallyAsync()
: // This method does not support App Store Connect API.
await profile.regenerateAsync(),
didUpdate: true,
};
}
// there is no valid provisioning profile available
@@ -173,7 +183,14 @@ async function manageAdHocProfilesAsync(
}
// We need to add new devices to the list and create a new provisioning profile.
existingProfile.attributes.devices = devices;
await existingProfile.regenerateAsync();
if (isAppStoreConnectTokenOnlyContext(existingProfile.context)) {
// Experimentally regenerate the provisioning profile using App Store Connect API.
await existingProfile.regenerateManuallyAsync();
} else {
// This method does not support App Store Connect API.
await existingProfile.regenerateAsync();
}
const updatedProfile = (await findProfileByBundleIdAsync(context, bundleId, certSerialNumber))
.profile;
@@ -5,7 +5,8 @@ import dateformat from 'dateformat';
import Log from '../../../log';
import { ora } from '../../../ora';
import { PushKey, PushKeyStoreInfo } from './Credentials.types';
import { AuthCtx, getRequestContext } from './authenticate';
import { getRequestContext } from './authenticate';
import { UserAuthCtx } from './authenticateTypes';
const { MaxKeysCreatedError } = Keys;
@@ -15,10 +16,14 @@ Please revoke the old ones or reuse existing from your other apps.
Please remember that Apple Keys are not application specific!
`;
export async function listPushKeysAsync(authCtx: AuthCtx): Promise<PushKeyStoreInfo[]> {
/**
* List all existing push keys on Apple servers.
* **Does not support App Store Connect API (CI).**
*/
export async function listPushKeysAsync(userAuthCtx: UserAuthCtx): Promise<PushKeyStoreInfo[]> {
const spinner = ora(`Fetching Apple push keys`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const keys = await Keys.getKeysAsync(context);
spinner.succeed(`Fetched Apple push keys`);
return keys;
@@ -28,21 +33,25 @@ export async function listPushKeysAsync(authCtx: AuthCtx): Promise<PushKeyStoreI
}
}
/**
* Create a new push key on Apple servers.
* **Does not support App Store Connect API (CI).**
*/
export async function createPushKeyAsync(
authCtx: AuthCtx,
userAuthCtx: UserAuthCtx,
name: string = `Expo Push Notifications Key ${dateformat('yyyymmddHHMMss')}`
): Promise<PushKey> {
const spinner = ora(`Creating Apple push key`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
const key = await Keys.createKeyAsync(context, { name, isApns: true });
const apnsKeyP8 = await Keys.downloadKeyAsync(context, { id: key.id });
spinner.succeed(`Created Apple push key`);
return {
apnsKeyId: key.id,
apnsKeyP8,
teamId: authCtx.team.id,
teamName: authCtx.team.name,
teamId: userAuthCtx.team.id,
teamName: userAuthCtx.team.name,
};
} catch (err: any) {
spinner.fail('Failed to create Apple push key');
@@ -57,12 +66,16 @@ export async function createPushKeyAsync(
}
}
export async function revokePushKeyAsync(authCtx: AuthCtx, ids: string[]): Promise<void> {
/**
* Revoke an existing push key on Apple servers.
* **Does not support App Store Connect API (CI).**
*/
export async function revokePushKeyAsync(userAuthCtx: UserAuthCtx, ids: string[]): Promise<void> {
const name = `Apple push key${ids?.length === 1 ? '' : 's'}`;
const spinner = ora(`Revoking ${name}`).start();
try {
const context = getRequestContext(authCtx);
const context = getRequestContext(userAuthCtx);
await Promise.all(ids.map(id => Keys.revokeKeyAsync(context, { id })));
spinner.succeed(`Revoked ${name}`);
@@ -5,6 +5,8 @@ import wrapAnsi from 'wrap-ansi';
import Log, { learnMore } from '../../../log';
import { promptAsync } from '../../../prompts';
import { MinimalAscApiKey } from '../credentials';
import { AppleTeamType, Team } from './authenticateTypes';
import * as Keychain from './keychain';
/**
@@ -13,7 +15,7 @@ import * as Keychain from './keychain';
*
* @param options
*/
export async function resolveCredentialsAsync(
export async function resolveUserCredentialsAsync(
options: Partial<Auth.UserCredentials>
): Promise<Partial<Auth.UserCredentials>> {
const credentials = getAppleIdFromEnvironmentOrOptions(options);
@@ -25,6 +27,162 @@ export async function resolveCredentialsAsync(
return credentials;
}
export function hasAscEnvVars(): boolean {
return (
!!process.env.EXPO_ASC_API_KEY_PATH ||
!!process.env.EXPO_ASC_KEY_ID ||
!!process.env.EXPO_ASC_ISSUER_ID
);
}
export async function resolveAscApiKeyAsync(
ascApiKey?: MinimalAscApiKey
): Promise<MinimalAscApiKey> {
const passedKeyP8 = await getAscKeyP8FromEnvironmentOrOptionsAsync(ascApiKey);
const passedKeyId = await getAscKeyIdFromEnvironmentOrOptionsAsync(ascApiKey);
const passedIssuerId = await getAscIssuerIdFromEnvironmentOrOptionsAsync(ascApiKey);
return {
keyP8: passedKeyP8,
keyId: passedKeyId,
issuerId: passedIssuerId,
};
}
async function getAscKeyP8FromEnvironmentOrOptionsAsync(
ascApiKey?: MinimalAscApiKey
): Promise<string> {
if (ascApiKey?.keyP8) {
return ascApiKey?.keyP8;
} else if (process.env.EXPO_ASC_API_KEY_PATH) {
return await fs.readFile(process.env.EXPO_ASC_API_KEY_PATH, 'utf-8');
}
const { ascApiKeyPath } = await promptAsync({
type: 'text',
name: 'ascApiKeyPath',
message: `Path to ASC Api Key Path (.p8):`,
validate: (val: string) => val !== '',
});
return await fs.readFile(ascApiKeyPath, 'utf-8');
}
async function getAscKeyIdFromEnvironmentOrOptionsAsync(
ascApiKey?: MinimalAscApiKey
): Promise<string> {
if (ascApiKey?.keyId) {
return ascApiKey?.keyId;
} else if (process.env.EXPO_ASC_KEY_ID) {
return process.env.EXPO_ASC_KEY_ID;
}
const { ascApiKeyId } = await promptAsync({
type: 'text',
name: 'ascApiKeyId',
message: `ASC Api Key ID:`,
validate: (val: string) => val !== '',
});
return ascApiKeyId;
}
async function getAscIssuerIdFromEnvironmentOrOptionsAsync(
ascApiKey?: MinimalAscApiKey
): Promise<string> {
if (ascApiKey?.issuerId) {
return ascApiKey?.issuerId;
} else if (process.env.EXPO_ASC_ISSUER_ID) {
return process.env.EXPO_ASC_ISSUER_ID;
}
const { ascIssuerId } = await promptAsync({
type: 'text',
name: 'ascIssuerId',
message: `ASC Issuer ID:`,
validate: (val: string) => val !== '',
});
return ascIssuerId;
}
function isAppleTeamType(maybeTeamType: any): maybeTeamType is AppleTeamType {
return maybeTeamType in AppleTeamType;
}
function assertAppleTeamType(maybeTeamType: any): AppleTeamType {
if (!isAppleTeamType(maybeTeamType)) {
throw new Error(
`Invalid Apple Team Type: ${maybeTeamType}. Must be one of ${Object.keys(AppleTeamType).join(
', '
)}`
);
}
return maybeTeamType;
}
function resolveAppleTeamTypeFromEnvironment(): AppleTeamType | undefined {
if (!process.env.EXPO_APPLE_TEAM_TYPE) {
return undefined;
}
return assertAppleTeamType(process.env.EXPO_APPLE_TEAM_TYPE);
}
async function getAppleTeamIdFromEnvironmentOrOptionsAsync(options: {
teamId?: string;
}): Promise<string> {
if (options.teamId) {
return options.teamId;
} else if (process.env.EXPO_APPLE_TEAM_ID) {
return process.env.EXPO_APPLE_TEAM_ID;
}
const { appleTeamId } = await promptAsync({
type: 'text',
name: 'appleTeamId',
message: `Apple Team ID:`,
validate: (val: string) => val !== '',
});
return appleTeamId;
}
async function getAppleTeamTypeFromEnvironmentOrOptionsAsync(options: {
teamType?: AppleTeamType;
}): Promise<string> {
if (options.teamType) {
return options.teamType;
}
const appleTeamTypeFromEnvironment = resolveAppleTeamTypeFromEnvironment();
if (appleTeamTypeFromEnvironment) {
return appleTeamTypeFromEnvironment;
}
const { appleTeamType } = await promptAsync({
type: 'select',
message: 'Select your Apple Team Type:',
name: 'appleTeamType',
choices: [
{ title: 'Enterprise', value: AppleTeamType.IN_HOUSE },
{ title: 'Company/Organization', value: AppleTeamType.COMPANY_OR_ORGANIZATION },
{ title: 'Individual', value: AppleTeamType.INDIVIDUAL },
],
});
return appleTeamType;
}
export async function resolveAppleTeamAsync(
options: {
teamId?: string;
teamName?: string;
teamType?: AppleTeamType;
} = {}
): Promise<Team> {
const passedTeamType = await getAppleTeamTypeFromEnvironmentOrOptionsAsync(options);
return {
id: await getAppleTeamIdFromEnvironmentOrOptionsAsync(options),
name: options.teamName,
inHouse: passedTeamType === AppleTeamType.IN_HOUSE,
};
}
function getAppleIdFromEnvironmentOrOptions({
username,
password,
@@ -0,0 +1,6 @@
import { RequestContext } from '@expo/apple-utils';
/** Is the request context App Store Connect only with no access to cookies authentication. */
export function isAppStoreConnectTokenOnlyContext(authContext: RequestContext): boolean {
return !authContext.teamId && !!authContext.token;
}
+1 -1
View File
@@ -97,7 +97,7 @@ export class AccountResolver {
async function ensureAppleTeamExistsAsync(
accountId: string,
{ appleTeamIdentifier, appleTeamName }: { appleTeamIdentifier: string; appleTeamName: string }
{ appleTeamIdentifier, appleTeamName }: { appleTeamIdentifier: string; appleTeamName?: string }
): Promise<AppleTeamFragment> {
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
accountId,
+9 -6
View File
@@ -10,18 +10,21 @@ export class RequestError extends Error {
}
}
function createHttpAgent(): Agent | null {
const httpProxyUrl = process.env.http_proxy;
if (!httpProxyUrl) {
function createHttpsAgent(): Agent | null {
const httpsProxyUrl = process.env.https_proxy;
if (!httpsProxyUrl) {
return null;
}
return createHttpsProxyAgent(httpProxyUrl);
return createHttpsProxyAgent(httpsProxyUrl);
}
export const httpProxyAgent: Agent | null = createHttpAgent();
export const httpsProxyAgent: Agent | null = createHttpsAgent();
export default async function (url: RequestInfo, init?: RequestInit): Promise<Response> {
const response = await fetch(url, init);
const response = await fetch(url, {
...init,
...(httpsProxyAgent ? { agent: httpsProxyAgent } : {}),
});
if (response.status >= 400) {
throw new RequestError(`Request failed: ${response.status} (${response.statusText})`, response);
}
+2 -2
View File
@@ -15,7 +15,7 @@ import { DocumentNode } from 'graphql';
import fetch from 'node-fetch';
import { getExpoApiBaseUrl } from '../api';
import { httpProxyAgent } from '../fetch';
import { httpsProxyAgent } from '../fetch';
import Log from '../log';
import { getAccessToken, getSessionSecret } from '../user/sessionStorage';
@@ -49,7 +49,7 @@ export const graphqlClient = createUrqlClient({
headers['expo-session'] = sessionSecret;
}
return {
...(httpProxyAgent ? { agent: httpProxyAgent } : {}),
...(httpsProxyAgent ? { agent: httpsProxyAgent } : {}),
headers,
};
},
+69 -5
View File
@@ -96,6 +96,7 @@ export type Account = {
availableBuilds?: Maybe<Scalars['Int']>;
/** Billing information */
billing?: Maybe<Billing>;
billingPeriod: BillingPeriod;
/** Build Jobs associated with this account */
buildJobs: Array<BuildJob>;
/**
@@ -129,6 +130,8 @@ export type Account = {
/** @deprecated See isCurrent */
unlimitedBuilds: Scalars['Boolean'];
updatedAt: Scalars['DateTime'];
/** Account query object for querying EAS usage metrics */
usageMetrics: AccountUsageMetrics;
/** Pending user invitations for this account */
userInvitations: Array<UserInvitation>;
/** Actors associated with this account and permissions they hold */
@@ -196,6 +199,15 @@ export type AccountAppsArgs = {
};
/**
* An account is a container owning projects, credentials, billing and other organization
* data and settings. Actors may own and be members of accounts.
*/
export type AccountBillingPeriodArgs = {
date: Scalars['DateTime'];
};
/**
* An account is a container owning projects, credentials, billing and other organization
* data and settings. Actors may own and be members of accounts.
@@ -379,6 +391,26 @@ export type AccountQueryByNameArgs = {
accountName: Scalars['String'];
};
export type AccountUsageMetric = {
__typename?: 'AccountUsageMetric';
metric: EasServiceMetric;
metricType: UsageMetricType;
timestamp: Scalars['DateTime'];
value: Scalars['Float'];
};
export type AccountUsageMetrics = {
__typename?: 'AccountUsageMetrics';
metricsForServiceMetric: Array<AccountUsageMetric>;
};
export type AccountUsageMetricsMetricsForServiceMetricArgs = {
granularity: UsageMetricsGranularity;
metric: EasServiceMetric;
timespan: UsageMetricsTimespan;
};
export type ActivityTimelineProjectActivity = {
activityTimestamp: Scalars['DateTime'];
actor?: Maybe<Actor>;
@@ -1482,6 +1514,13 @@ export type Billing = {
subscription?: Maybe<SubscriptionDetails>;
};
export type BillingPeriod = {
__typename?: 'BillingPeriod';
anchor: Scalars['DateTime'];
end: Scalars['DateTime'];
start: Scalars['DateTime'];
};
/** Represents an EAS Build */
export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
__typename?: 'Build';
@@ -1496,9 +1535,12 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
createdAt?: Maybe<Scalars['DateTime']>;
distribution?: Maybe<DistributionType>;
error?: Maybe<BuildError>;
estimatedWaitTimeLeftSeconds?: Maybe<Scalars['Int']>;
expirationDate?: Maybe<Scalars['DateTime']>;
gitCommitHash?: Maybe<Scalars['String']>;
id: Scalars['ID'];
/** Queue position is 1-indexed */
initialQueuePosition?: Maybe<Scalars['Int']>;
initiatingActor?: Maybe<Actor>;
/** @deprecated User type is deprecated */
initiatingUser?: Maybe<User>;
@@ -2014,6 +2056,10 @@ export enum EasBuildDeprecationInfoType {
UserFacing = 'USER_FACING'
}
export enum EasServiceMetric {
ManifestRequests = 'MANIFEST_REQUESTS'
}
export type EditUpdateBranchInput = {
appId?: InputMaybe<Scalars['ID']>;
id?: InputMaybe<Scalars['ID']>;
@@ -3349,6 +3395,22 @@ export enum UploadSessionType {
EasSubmitAppArchive = 'EAS_SUBMIT_APP_ARCHIVE'
}
export enum UsageMetricType {
Request = 'REQUEST'
}
export enum UsageMetricsGranularity {
Day = 'DAY',
Hour = 'HOUR',
Minute = 'MINUTE',
Total = 'TOTAL'
}
export type UsageMetricsTimespan = {
end: Scalars['DateTime'];
start: Scalars['DateTime'];
};
/** Represents a human (not robot) actor. */
export type User = Actor & {
__typename?: 'User';
@@ -3470,6 +3532,7 @@ export type UserInvitation = {
created: Scalars['DateTime'];
/** Email to which this invitation was sent */
email: Scalars['String'];
expires: Scalars['DateTime'];
id: Scalars['ID'];
/** Account permissions to be granted upon acceptance of this invitation */
permissions: Array<Permission>;
@@ -3541,6 +3604,7 @@ export type UserInvitationPublicData = {
accountName: Scalars['String'];
created: Scalars['DateTime'];
email: Scalars['String'];
expires: Scalars['DateTime'];
/** Email to which this invitation was sent */
id: Scalars['ID'];
};
@@ -4193,7 +4257,7 @@ export type CreateAndroidBuildMutationVariables = Exact<{
}>;
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateAndroidBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createAndroidBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateIosBuildMutationVariables = Exact<{
appId: Scalars['ID'];
@@ -4202,7 +4266,7 @@ export type CreateIosBuildMutationVariables = Exact<{
}>;
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateIosBuildMutation = { __typename?: 'RootMutation', build: { __typename?: 'BuildMutation', createIosBuild: { __typename?: 'CreateBuildResult', build: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }, deprecationInfo?: { __typename?: 'EASBuildDeprecationInfo', type: EasBuildDeprecationInfoType, message: string } | null } } };
export type CreateEnvironmentSecretForAccountMutationVariables = Exact<{
input: CreateEnvironmentSecretInput;
@@ -4315,7 +4379,7 @@ export type BuildsByIdQueryVariables = Exact<{
}>;
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type BuildsByIdQuery = { __typename?: 'RootQuery', builds: { __typename?: 'BuildQuery', byId: { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } } } };
export type GetAllBuildsForAppQueryVariables = Exact<{
appId: Scalars['String'];
@@ -4325,7 +4389,7 @@ export type GetAllBuildsForAppQueryVariables = Exact<{
}>;
export type GetAllBuildsForAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
export type GetAllBuildsForAppQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, builds: Array<{ __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } }> } } };
export type GetChannelByNameForAppQueryVariables = Exact<{
appId: Scalars['String'];
@@ -4421,7 +4485,7 @@ export type WebhookByIdQuery = { __typename?: 'RootQuery', webhook: { __typename
export type AppFragment = { __typename?: 'App', id: string, fullName: string, slug: string };
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type BuildFragment = { __typename?: 'Build', id: string, status: BuildStatus, platform: AppPlatform, channel?: string | null, releaseChannel?: string | null, distribution?: DistributionType | null, iosEnterpriseProvisioning?: BuildIosEnterpriseProvisioning | null, buildProfile?: string | null, sdkVersion?: string | null, appVersion?: string | null, appBuildVersion?: string | null, runtimeVersion?: string | null, gitCommitHash?: string | null, initialQueuePosition?: number | null, queuePosition?: number | null, estimatedWaitTimeLeftSeconds?: number | null, priority: BuildPriority, createdAt?: any | null, updatedAt?: any | null, error?: { __typename?: 'BuildError', errorCode: string, message: string, docsUrl?: string | null } | null, artifacts?: { __typename?: 'BuildArtifacts', buildUrl?: string | null, xcodeBuildLogsUrl?: string | null } | null, initiatingActor?: { __typename: 'Robot', id: string, displayName: string } | { __typename: 'User', id: string, displayName: string } | null, project: { __typename: 'App', id: string, name: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } } | { __typename: 'Snack', id: string, name: string, slug: string } };
export type EnvironmentSecretFragment = { __typename?: 'EnvironmentSecret', id: string, name: string, createdAt: any };
@@ -41,6 +41,10 @@ export const BuildFragmentNode = gql`
appBuildVersion
runtimeVersion
gitCommitHash
initialQueuePosition
queuePosition
estimatedWaitTimeLeftSeconds
priority
createdAt
updatedAt
}
@@ -1,12 +1,27 @@
import { ExpoConfig } from '@expo/config';
import { IOSConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import Joi from 'joi';
import { Target } from '../../credentials/ios/types';
import { resolveWorkflowAsync } from '../workflow';
import { getBundleIdentifierAsync } from './bundleIdentifier';
import { XcodeBuildContext } from './scheme';
interface UserDefinedTarget {
targetName: string;
bundleIdentifier: string;
parentBundleIdentifier?: string;
}
const AppExtensionsConfigSchema = Joi.array().items(
Joi.object({
targetName: Joi.string().required(),
bundleIdentifier: Joi.string().required(),
parentBundleIdentifier: Joi.string(),
})
);
export async function resolveTargetsAsync(
{ exp, projectDir }: { exp: ExpoConfig; projectDir: string },
{ buildConfiguration, buildScheme }: XcodeBuildContext
@@ -35,9 +50,54 @@ export async function resolveTargetsAsync(
result.push(...dependencies);
}
result.push(
...(await resolveManagedAppExtensionsAsync({
exp,
projectDir,
buildConfiguration,
applicationTargetBundleIdentifier: bundleIdentifier,
}))
);
return result;
}
async function resolveManagedAppExtensionsAsync({
exp,
projectDir,
buildConfiguration,
applicationTargetBundleIdentifier,
}: {
exp: ExpoConfig;
projectDir: string;
buildConfiguration?: string;
applicationTargetBundleIdentifier: string;
}): Promise<Target[]> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
const managedAppExtensions: UserDefinedTarget[] =
exp.extra?.eas?.build?.experimental?.ios?.appExtensions;
if (workflow === Workflow.GENERIC || !managedAppExtensions) {
return [];
}
const { error } = AppExtensionsConfigSchema.validate(managedAppExtensions, {
allowUnknown: false,
abortEarly: false,
});
if (error) {
throw new Error(
`Failed to validate "extra.eas.build.experimental.ios.appExtensions" in you app config\n${error.message}`
);
}
return managedAppExtensions.map(extension => ({
targetName: extension.targetName,
buildConfiguration,
bundleIdentifier: extension.bundleIdentifier,
parentBundleIdentifier: extension.parentBundleIdentifier ?? applicationTargetBundleIdentifier,
}));
}
async function resolveDependenciesAsync({
exp,
projectDir,
@@ -24,7 +24,6 @@ interface CreateAppOptions {
}
type AppStoreResult = {
appleIdUsername: string;
ascAppIdentifier: string;
};
@@ -67,29 +66,29 @@ async function createAppStoreConnectAppAsync(
sku,
} = options;
const authCtx = await ctx.credentialsCtx.appStore.ensureAuthenticatedAsync({
const userAuthCtx = await ctx.credentialsCtx.appStore.ensureUserAuthenticatedAsync({
appleId,
teamId: appleTeamId,
});
const requestCtx = getRequestContext(authCtx);
const requestCtx = getRequestContext(userAuthCtx);
Log.addNewLineIfNone();
if (await isProvisioningAvailableAsync(requestCtx)) {
await ensureBundleIdExistsWithNameAsync(authCtx, {
await ensureBundleIdExistsWithNameAsync(userAuthCtx, {
name: appName,
bundleIdentifier: bundleId,
});
} else {
Log.warn(
`Provisioning is not available for user "${authCtx.appleId}", skipping bundle identifier check.`
`Provisioning is not available for Apple User: ${userAuthCtx.appleId}, skipping bundle identifier check.`
);
}
let app: App | null = null;
try {
app = await ensureAppExistsAsync(authCtx, {
app = await ensureAppExistsAsync(userAuthCtx, {
name: appName,
language,
companyName,
@@ -120,7 +119,6 @@ async function createAppStoreConnectAppAsync(
}
return {
appleIdUsername: authCtx.appleId,
ascAppIdentifier: app.id,
};
}
@@ -1,6 +1,7 @@
import { Platform } from '@expo/eas-build-job';
import getenv from 'getenv';
import { isUserAuthCtx } from '../../credentials/ios/appstore/authenticate';
import { promptAsync } from '../../prompts';
import UserSettings from '../../user/UserSettings';
import { SubmissionContext } from '../context';
@@ -51,7 +52,7 @@ export async function getAppleIdUsernameAsync(
return envAppleId;
}
if (ctx.credentialsCtx.appStore.authCtx?.appleId) {
if (isUserAuthCtx(ctx.credentialsCtx.appStore.authCtx)) {
return ctx.credentialsCtx.appStore.authCtx.appleId;
}
@@ -8,7 +8,6 @@ import {
getCodeSigningInfoAsync,
getKeyAndCertificateFromPathsAsync,
getManifestBodyAsync,
parseMultipartMixedResponseAsync,
} from '../code-signing';
function generateMultipartBody(stringifiedManifest: string): FormData {
@@ -92,28 +91,13 @@ describe(getKeyAndCertificateFromPathsAsync, () => {
});
});
describe(parseMultipartMixedResponseAsync, () => {
it('parses multipart response', async () => {
const form = generateMultipartBody(JSON.stringify({ hello: 'world' }));
const parts = await parseMultipartMixedResponseAsync({
arrayBuffer: async () => form.getBuffer(),
headers: new Headers({
'content-type': `multipart/mixed; boundary=${form.getBoundary()}`,
}),
} as any as Response);
expect(parts).toHaveLength(2);
});
});
describe(getManifestBodyAsync, () => {
it('gets multipart manifest body', async () => {
const stringifiedManifest = JSON.stringify({ hello: 'world' });
const form = generateMultipartBody(stringifiedManifest);
const body = await getManifestBodyAsync({
arrayBuffer: async () => form.getBuffer(),
arrayBuffer: async () => new Uint8Array(form.getBuffer()).buffer,
headers: new Headers({
'content-type': `multipart/mixed; boundary=${form.getBoundary()}`,
}),
+10 -58
View File
@@ -5,14 +5,15 @@ import {
validateSelfSignedCertificate,
} from '@expo/code-signing-certificates';
import { ExpoConfig } from '@expo/config';
import Dicer from 'dicer';
import {
isMultipartPartWithName,
parseMultipartMixedResponseAsync,
} from '@expo/multipart-body-parser';
import isDeepEqual from 'fast-deep-equal';
import { promises as fs } from 'fs';
import { pki as PKI } from 'node-forge';
import nullthrows from 'nullthrows';
import path from 'path';
import { Stream } from 'stream';
import { parseItem } from 'structured-headers';
import { Response } from '../fetch';
import { PartialManifest, PartialManifestAsset } from '../graphql/generated';
@@ -103,66 +104,17 @@ export async function getKeyAndCertificateFromPathsAsync({
};
}
export type MultipartPart = { headers: Map<string, string>; body: string };
export async function parseMultipartMixedResponseAsync(res: Response): Promise<MultipartPart[]> {
export async function getManifestBodyAsync(res: Response): Promise<string | null> {
const contentType = res.headers.get('content-type');
if (!contentType) {
throw new Error('The multipart manifest response is missing the content-type header');
}
const boundaryRegex = /^multipart\/.+?; boundary=(?:"([^"]+)"|([^\s;]+))/i;
const matches = boundaryRegex.exec(contentType);
if (!matches) {
throw new Error('The content-type header in the HTTP response is not a multipart media type');
}
const boundary = matches[1] ?? matches[2];
const bodyBuffer = await res.arrayBuffer();
const bufferStream = new Stream.PassThrough();
bufferStream.end(bodyBuffer);
return await new Promise((resolve, reject) => {
const parts: MultipartPart[] = [];
bufferStream.pipe(
new Dicer({ boundary })
.on('part', p => {
const part: MultipartPart = {
body: '',
headers: new Map(),
};
p.on('header', headers => {
for (const h in headers) {
part.headers.set(h, (headers as { [key: string]: string[] })[h][0]);
}
});
p.on('data', data => {
part.body += data.toString();
});
p.on('end', () => {
parts.push(part);
});
})
.on('finish', () => {
resolve(parts);
})
.on('error', error => {
reject(error);
})
);
});
}
function isManifestMultipartPart(multipartPart: MultipartPart): boolean {
const [, parameters] = parseItem(nullthrows(multipartPart.headers.get('content-disposition')));
const partName = parameters.get('name');
return partName === 'manifest';
}
export async function getManifestBodyAsync(res: Response): Promise<string | null> {
const multipartParts = await parseMultipartMixedResponseAsync(res);
const manifestPart = multipartParts.find(isManifestMultipartPart);
const multipartParts = await parseMultipartMixedResponseAsync(
contentType,
Buffer.from(bodyBuffer)
);
const manifestPart = multipartParts.find(part => isMultipartPartWithName(part, 'manifest'));
return manifestPart?.body ?? null;
}
+3 -3
View File
@@ -1,11 +1,11 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with eas.json",
"version": "0.48.0",
"version": "0.50.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/eas-build-job": "0.2.66",
"@expo/eas-build-job": "0.2.68",
"@expo/json-file": "8.2.34",
"chalk": "4.1.2",
"env-string": "1.0.1",
@@ -18,7 +18,7 @@
"devDependencies": {
"@types/fs-extra": "9.0.13",
"memfs": "3.4.1",
"typescript": "4.6.2"
"typescript": "4.6.3"
},
"engines": {
"node": ">=12.0.0"
Generated
+112 -63
View File
@@ -1178,10 +1178,10 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@expo/[email protected].28":
version "0.0.0-alpha.28"
resolved "https://registry.yarnpkg.com/@expo/apple-utils/-/apple-utils-0.0.0-alpha.28.tgz#fd8045f25ae9000471ce000093080ccd319cfb59"
integrity sha512-0BgHkNlzF21MQccCoAZ9h331Xej2TKjs7UZkzktUlvdpAjnpj5LC58TMXTKuPrpGzPzrzESbUzXHkt4hB60NSg==
"@expo/[email protected].30":
version "0.0.0-alpha.30"
resolved "https://registry.yarnpkg.com/@expo/apple-utils/-/apple-utils-0.0.0-alpha.30.tgz#5eb0ba660c1d099c6ea2b4d2ee25272ba041377e"
integrity sha512-ZlQCYBxHY931Tm3KWY6/v2EdqMsWWov3c+0Yq36i+yXvLpqTHiy7feMSb3s5QCDXEd1z0JKWbVPcpEKs/SAvXQ==
"@expo/bunyan@^4.0.0":
version "4.0.0"
@@ -1245,10 +1245,10 @@
slugify "^1.3.4"
sucrase "^3.20.0"
"@expo/[email protected]6":
version "0.2.66"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.66.tgz#0e13bd94d3af7d52052e241c93a628cfceac5960"
integrity sha512-MTJ1dX914f3rwgvqvdE/W6beVwTyfUPkUZHHEOT7KB0K6sNBPcn5ZS6My3mu/BHI5B0RF1M8cTvY9c2wAME3mA==
"@expo/[email protected]8":
version "0.2.68"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.68.tgz#7ab435de270abad78b941dce97e1f4c8ecf286e3"
integrity sha512-lP1Jki9K05jCsswjG6l/EXQJIwRcj1q0Ykl4J3s7PgAtNgbsHJpD2ksgX2zhJdxofRoIy4trMMUea5HzdATWuA==
dependencies:
joi "^17.4.2"
semver "^7.3.5"
@@ -1279,6 +1279,15 @@
json5 "^1.0.1"
write-file-atomic "^2.3.0"
"@expo/[email protected]":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@expo/multipart-body-parser/-/multipart-body-parser-1.1.0.tgz#ba402b3047cf14ef17b4d785ec030dd6474ae6e4"
integrity sha512-XOaS79wFIJgx0J7oUzRb+kZsnZmFqGpisu0r8RPO3b0wjbW7xpWgiXmRR4RavKeGiVAPauZOi4vad7cJ3KCspg==
dependencies:
dicer "^0.3.1"
nullthrows "^1.1.1"
structured-headers "^0.4.1"
"@expo/[email protected]":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@expo/pkcs12/-/pkcs12-0.0.7.tgz#4938b4e1f28e4910cd6a73b9b1674c751579025c"
@@ -1481,32 +1490,32 @@
"@graphql-tools/utils" "^8.1.1"
tslib "~2.3.0"
"@graphql-codegen/[email protected].4":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-2.3.4.tgz#3341f940d7fae0417e5738787fa8793696277d54"
integrity sha512-Jnsx+YTCqwq8F0ebyZAJGVtt+PQNuB/0uv9mEMbgcUbo/7w6EX6NZ8JArRuuz8MUb/gbpeO+GkfuNX+F6A8ikw==
"@graphql-codegen/[email protected].5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-2.3.5.tgz#1e77b96da0949f9e0ecd6054eb28b582936e35e8"
integrity sha512-GCZQW+O+cIF62ioPkQMoSJGzjJhtr7ttZGJOAoN/Q/oolG8ph9jNFePKO67tSQ/POAs5HLqfat4kAlCK8OPV3Q==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-codegen/typescript" "^2.4.7"
"@graphql-codegen/visitor-plugin-common" "2.7.3"
"@graphql-codegen/typescript" "^2.4.8"
"@graphql-codegen/visitor-plugin-common" "2.7.4"
auto-bind "~4.0.0"
tslib "~2.3.0"
"@graphql-codegen/[email protected].7", "@graphql-codegen/typescript@^2.4.7":
version "2.4.7"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-2.4.7.tgz#e72e87cd47a51b2bc232293762573a86182de469"
integrity sha512-1YAK+m9SZRS1Uy8hdXVQAaAG/WF2jdFD76wbMRQ8Lf5e9YbmtJuy1RpI13nZJTZbhFopd9zqoWFJHsU/oy4i7g==
"@graphql-codegen/[email protected].8", "@graphql-codegen/typescript@^2.4.8":
version "2.4.8"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-2.4.8.tgz#e8110baba9713cde72d57a5c95aa27400363ed9a"
integrity sha512-tVsHIkuyenBany7c5IMU1yi4S1er2hgyXJGNY7PcyhpJMx0eChmbqz1VTiZxDEwi8mDBS2mn3TaSJMh6xuJM5g==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-codegen/schema-ast" "^2.4.1"
"@graphql-codegen/visitor-plugin-common" "2.7.3"
"@graphql-codegen/visitor-plugin-common" "2.7.4"
auto-bind "~4.0.0"
tslib "~2.3.0"
"@graphql-codegen/[email protected].3":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.3.tgz#f374e8974ae4ab7b1059a7f8748db6393f20c138"
integrity sha512-rODPGkrHEf9aHD8SgVWS0xie8VmJoYXLWgQR8PFKrwCUFjf7nvDuATAL2lTFiHATlVBWg1meXVPdUlkKeJj/0Q==
"@graphql-codegen/[email protected].4":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.4.tgz#fbc8aec9df0035b8f29fa64a6356cbb02062da5d"
integrity sha512-aaDoEudDD+B7DK/UwDSL2Fzej75N9hNJ3N8FQuTIeDyw6FNGWUxmkjVBLQGlzfnYfK8IYkdfYkrPn3Skq0pVxA==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-tools/optimize" "^1.0.1"
@@ -2994,10 +3003,10 @@
supports-color "^8.1.1"
tslib "^2"
"@oclif/core@1.5.3", "@oclif/core@^1.3.6":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.5.3.tgz#472e427ba4f6f61cefe877a22b8c73bcfd9bee3e"
integrity sha512-m0I74bRXmNqMX1hscxdF5u/Lw0sB+vwoGnycFO2QzOq8jOphRQxYuUoVWPMQFNmVSjgVEShoqxMDxjgoCKsKoQ==
"@oclif/core@1.6.4", "@oclif/core@^1.6.4":
version "1.6.4"
resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.6.4.tgz#fd213265199a8ef93adb358e15a04d74927eb522"
integrity sha512-eUnh03MWxs3PHQUdZbo43ceLqmeOgGegsfimeqd6xfhcKwSv8dqarRyAoMCjg7P6Qm5qCjaNFZ6eS4ao349xvQ==
dependencies:
"@oclif/linewrap" "^1.0.0"
"@oclif/screen" "^3.0.2"
@@ -3122,6 +3131,41 @@
widest-line "^3.1.0"
wrap-ansi "^7.0.0"
"@oclif/core@^1.3.6":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.5.3.tgz#472e427ba4f6f61cefe877a22b8c73bcfd9bee3e"
integrity sha512-m0I74bRXmNqMX1hscxdF5u/Lw0sB+vwoGnycFO2QzOq8jOphRQxYuUoVWPMQFNmVSjgVEShoqxMDxjgoCKsKoQ==
dependencies:
"@oclif/linewrap" "^1.0.0"
"@oclif/screen" "^3.0.2"
ansi-escapes "^4.3.2"
ansi-styles "^4.3.0"
cardinal "^2.1.1"
chalk "^4.1.2"
clean-stack "^3.0.1"
cli-progress "^3.10.0"
debug "^4.3.3"
ejs "^3.1.6"
fs-extra "^9.1.0"
get-package-type "^0.1.0"
globby "^11.1.0"
hyperlinker "^1.0.0"
indent-string "^4.0.0"
is-wsl "^2.2.0"
js-yaml "^3.14.1"
lodash "^4.17.21"
natural-orderby "^2.0.3"
object-treeify "^1.1.33"
password-prompt "^1.1.2"
semver "^7.3.5"
string-width "^4.2.3"
strip-ansi "^6.0.1"
supports-color "^8.1.1"
supports-hyperlinks "^2.2.0"
tslib "^2.3.1"
widest-line "^3.1.0"
wrap-ansi "^7.0.0"
"@oclif/linewrap@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91"
@@ -3458,6 +3502,13 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/[email protected]":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.9.2.tgz#6ca355f96268af39bee9f9307f0ac96145639c26"
integrity sha512-VO5/X5Ij+oVgEVjg5u0IXVe3JQSKJX+Ev8C5x+0hPy0AuWyW+bF8tbajR7cPFnDGhs7pidztcac+ccrDtk5teA==
dependencies:
"@types/node" "*"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -3468,13 +3519,6 @@
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc"
integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==
"@types/[email protected]":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@types/dicer/-/dicer-0.2.2.tgz#61e3a26ea4bf41cd003fd6f7adb9c78649f753de"
integrity sha512-UPLqCYey+jn5Mf57KFDwxD/7VZYDsbYUi3iyTehLFVjlbvl/JcUTPaot8uKNYLO0EoZpey+rC/s5AF3VxfeC2Q==
dependencies:
"@types/node" "*"
"@types/[email protected]":
version "7.8.1"
resolved "https://registry.yarnpkg.com/@types/envinfo/-/envinfo-7.8.1.tgz#1915df82c16d637e92146645c70db9360eb099c6"
@@ -4812,7 +4856,7 @@ cli-cursor@^3.1.0:
dependencies:
restore-cursor "^3.1.0"
cli-progress@^3.10.0:
[email protected], cli-progress@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.10.0.tgz#63fd9d6343c598c93542fdfa3563a8b59887d78a"
integrity sha512-kLORQrhYCAtUPLZxqsAt2YJGOvRdt34+O6jl5cQGb7iF3dM55FQZlTR+rQyIK9JUcO9bBMwZsTlND+3dmFU2Cw==
@@ -5592,7 +5636,7 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
[email protected]:
dicer@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.1.tgz#abf28921e3475bc5e801e74e0159fd94f927ba97"
integrity sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==
@@ -6092,10 +6136,10 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
[email protected]1.0:
version "8.11.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37"
integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==
[email protected]2.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e"
integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==
dependencies:
"@eslint/eslintrc" "^1.2.1"
"@humanwhocodes/config-array" "^0.9.2"
@@ -9505,10 +9549,10 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
[email protected].1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
[email protected].2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557"
integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==
natural-compare@^1.4.0:
version "1.4.0"
@@ -9588,7 +9632,12 @@ [email protected], node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5:
dependencies:
whatwg-url "^5.0.0"
node-forge@1.2.1, node-forge@^1.2.1:
node-forge@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
node-forge@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c"
integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==
@@ -9981,12 +10030,12 @@ object.values@^1.1.5:
define-properties "^1.1.3"
es-abstract "^1.19.1"
oclif@2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/oclif/-/oclif-2.5.0.tgz#90be1ee7eb8f0aab246e31debb58466d7d7535bf"
integrity sha512-Y3FJ/u7J7TwDIFoyL05U1cOPiI/e3Bz1KXeTq9lwz0Ovda6q9H+5jaHlorGPWrvMb5Tq92OKRv7rVF1RQebcrg==
oclif@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/oclif/-/oclif-2.6.3.tgz#77295d8342a41bfebcf13f7eba260d26ff5d92ef"
integrity sha512-t3w1GnDrZ9GnlO2WxdTRIlb5EmfLo7fkcpKo95q+03HoL4GVUNneraVymyUTYEBsvMyWcX8PbVkcMai/HhBydA==
dependencies:
"@oclif/core" "^1.3.0"
"@oclif/core" "^1.6.4"
"@oclif/plugin-help" "^5.1.11"
"@oclif/plugin-not-found" "^2.3.1"
"@oclif/plugin-warn-if-update-available" "^2.0.4"
@@ -10576,10 +10625,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
prettier@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
pretty-bytes@^5.3.0:
version "5.6.0"
@@ -11793,7 +11842,7 @@ strong-log-transformer@^2.1.0:
minimist "^1.2.0"
through "^2.3.4"
[email protected]:
structured-headers@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1"
integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==
@@ -12175,10 +12224,10 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
[email protected].3:
version "27.1.3"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.3.tgz#1f723e7e74027c4da92c0ffbd73287e8af2b2957"
integrity sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==
[email protected].4:
version "27.1.4"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00"
integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==
dependencies:
bs-logger "0.x"
fast-json-stable-stringify "2.x"
@@ -12352,10 +12401,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
[email protected].2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
[email protected].3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
ua-parser-js@^0.7.30:
version "0.7.31"