mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
878028271a | ||
|
|
d4e15bbcbf | ||
|
|
13bb056df1 | ||
|
|
14d56904d2 | ||
|
|
c2ffd05763 | ||
|
|
46e394fbef | ||
|
|
2613f0c2f9 | ||
|
|
e5fd7c01f4 | ||
|
|
73a0dd6e23 | ||
|
|
a9dc7f9eeb | ||
|
|
c3836f23d1 | ||
|
|
bfd73cbb19 | ||
|
|
6a1753edaa | ||
|
|
558a73086d | ||
|
|
15966fbd26 | ||
|
|
1e8b55383e | ||
|
|
6920ebb0ba | ||
|
|
55f35949e1 | ||
|
|
ccb998a221 | ||
|
|
30bc27b4c4 | ||
|
|
da3c95ccaf | ||
|
|
bbcbe1dceb | ||
|
|
f15beea454 | ||
|
|
16e384be76 | ||
|
|
e74e6d8fc5 | ||
|
|
b6f4603107 | ||
|
|
0e7d7f765a | ||
|
|
33b4d8d719 | ||
|
|
0d765748e3 | ||
|
|
93953f1424 | ||
|
|
e9b67db05c | ||
|
|
09bf912b9e | ||
|
|
e701bacdaa | ||
|
|
cc22542031 | ||
|
|
6c53262b5b | ||
|
|
7d7d4995d8 | ||
|
|
977546c8c9 | ||
|
|
53dcd4cb43 | ||
|
|
9e18e738eb | ||
|
|
252c7187d5 | ||
|
|
90852b9557 | ||
|
|
0df16236a2 | ||
|
|
ecaf5a4318 | ||
|
|
86e5e1a7b1 | ||
|
|
ab10094071 | ||
|
|
2d536d73fe | ||
|
|
80ea8ee50b | ||
|
|
b49c5f47d1 |
No files matched your search
@@ -10,6 +10,7 @@ module.exports = {
|
||||
jasmine: false,
|
||||
},
|
||||
rules: {
|
||||
'no-console':'warn',
|
||||
'no-constant-condition': ['warn', { checkLoops: false }],
|
||||
'sort-imports': [
|
||||
'warn',
|
||||
@@ -18,6 +19,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'flowtype/no-types-missing-file-annotation': 'off',
|
||||
'curly': 'warn',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
**/yarn.lock linguist-generated
|
||||
@@ -11,3 +11,4 @@ tmp
|
||||
node_modules
|
||||
coverage
|
||||
.history
|
||||
.DS_Store
|
||||
+68
-2
@@ -8,14 +8,80 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Added support for automatically creating and linking iOS capability identifiers (Apple Pay, iCloud Containers, App Groups). ([#524](https://github.com/expo/eas-cli/pull/524) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Add `secret:create --force` command to overwrite existing secrets. ([#513](https://github.com/expo/eas-cli/pull/513) by [@bycedric](https://github.com/bycedric))
|
||||
- Fall back to APK when building for internal distribution. ([#527](https://github.com/expo/eas-cli/pull/527) by [@dsokal](https://github.com/dsokal))
|
||||
- Add `iosEnterpriseProvisioning` to build metadata. ([#522](https://github.com/expo/eas-cli/pull/522) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix iOS capability syncing on build. ([#521](https://github.com/expo/eas-cli/pull/521) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Fix unhandled error when amplitude domains are blocked. ([#512](https://github.com/expo/eas-cli/pull/512) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
- Use default value for `appBuildVersion` in build metadata when building an Android managed project. ([#526](https://github.com/expo/eas-cli/pull/526) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.21.0](https://github.com/expo/eas-cli/releases/tag/v0.21.0) - 2021-07-12
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `project:*` commands. ([#500](https://github.com/expo/eas-cli/pull/500) by [@jkhales](https://github.com/jkhales))
|
||||
- Added support for iOS 15 capabilities: Communication Notifications, Time Sensitive Notifications, Group Activities, and Family Controls. ([#499](https://github.com/expo/eas-cli/pull/499) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Show more build metadata in `build:view` and `build:list`. ([#504](https://github.com/expo/eas-cli/pull/504) and [#508](https://github.com/expo/eas-cli/pull/508) by [@dsokal](https://github.com/dsokal))
|
||||
- Add runtime version to build metadata. ([#493](https://github.com/expo/eas-cli/pull/493) by [@dsokal](https://github.com/dsokal))
|
||||
|
||||
## [0.20.0](https://github.com/expo/eas-cli/releases/tag/v0.20.0) - 2021-07-09
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
- Unifify generic and managed workflow, deprecate `workflow` field. ([#497](https://github.com/expo/eas-cli/pull/497) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix runtime version checks. ([#495](https://github.com/expo/eas-cli/pull/495) by [@dsokal](https://github.com/dsokal))
|
||||
- Resolve `--android-package` correctly in `eas submit` command. ([#494](https://github.com/expo/eas-cli/pull/494) by [@wkozyra95](https://github.com/wkozyra95))
|
||||
|
||||
## [0.19.1](https://github.com/expo/eas-cli/releases/tag/v0.19.1) - 2021-07-02
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Improve project workflow detection (fixes the case where the `android` and/or `ios` directories are git-ignored). ([#490](https://github.com/expo/eas-cli/pull/490) by [@dsokal](https://github.com/dsokal))
|
||||
- Improve credentials workflow with project creation and current working directories ([#491](https://github.com/expo/eas-cli/pull/491) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Better debugging experience ([#492](https://github.com/expo/eas-cli/pull/492) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.19.0](https://github.com/expo/eas-cli/releases/tag/v0.19.0) - 2021-07-01
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Auto-suggest application id and bundle identifier when running `eas build:configure` for a managed project. ([#487](https://github.com/expo/eas-cli/pull/487) by [@dsokal](https://github.com/dsokal))
|
||||
- Support configuring one platform at a time when running `eas build`. ([#483](https://github.com/expo/eas-cli/pull/483) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
## [0.18.3](https://github.com/expo/eas-cli/releases/tag/v0.18.3) - 2021-06-28
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Support bundle identifiers that begin with `.`. ([#459](https://github.com/expo/eas-cli/pull/459) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Push Key setup integrated with ios build command ([#473](https://github.com/expo/eas-cli/pull/473) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix android builds for PKCS12 keystores. ([#484](https://github.com/expo/eas-cli/pull/484) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
## [0.18.2](https://github.com/expo/eas-cli/releases/tag/v0.18.2) - 2021-06-25
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Change default iOS image for projects with Expo SDK <= 41 (SDK 41 won't build with Xcode 12.5). ([#479](https://github.com/expo/eas-cli/pull/479) by [@dsokal](https://github.com/dsokal))
|
||||
- Make Apple team optional in appropriate cases. ([#468](https://github.com/expo/eas-cli/pull/468) by [@quinlanj](https://github.com/quinlanj))
|
||||
- Show projects that depend on a push key. ([#472](https://github.com/expo/eas-cli/pull/472) by [@quinlanj](https://github.com/quinlanj))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Fix inverted conditional so we actually only prompt about commit when index is dirty. ([#481](https://github.com/expo/eas-cli/pull/481) by [@brentvatne](https://github.com/brentvatne))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [0.18.1](https://github.com/expo/eas-cli/releases/tag/v0.18.1) - 2021-06-24
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.18.2"
|
||||
"version": "0.22.0"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
graphql.schema.json linguist-generated
|
||||
src/graphql/generated.ts linguist-generated
|
||||
+52
-23
@@ -47,6 +47,8 @@ eas --help COMMAND
|
||||
* [`eas device:list`](#eas-devicelist)
|
||||
* [`eas device:view [UDID]`](#eas-deviceview-udid)
|
||||
* [`eas help [COMMAND]`](#eas-help-command)
|
||||
* [`eas project:info`](#eas-projectinfo)
|
||||
* [`eas project:init`](#eas-projectinit)
|
||||
* [`eas secret:create`](#eas-secretcreate)
|
||||
* [`eas secret:delete`](#eas-secretdelete)
|
||||
* [`eas secret:list`](#eas-secretlist)
|
||||
@@ -69,7 +71,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -83,7 +85,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -97,7 +99,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -108,7 +110,7 @@ USAGE
|
||||
$ eas analytics [STATUS]
|
||||
```
|
||||
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -128,7 +130,7 @@ OPTIONS
|
||||
--[no-]wait Wait for build(s) to complete
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -139,7 +141,7 @@ USAGE
|
||||
$ eas build:cancel [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -153,7 +155,7 @@ OPTIONS
|
||||
-p, --platform=(android|ios|all) Platform to configure
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -169,7 +171,7 @@ OPTIONS
|
||||
--status=(new|in-queue|in-progress|errored|finished|canceled)
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -180,7 +182,7 @@ USAGE
|
||||
$ eas build:view [BUILD_ID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -191,7 +193,7 @@ USAGE
|
||||
$ eas credentials
|
||||
```
|
||||
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/credentials.ts)_
|
||||
_See code: [src/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/credentials.ts)_
|
||||
|
||||
## `eas device:create`
|
||||
|
||||
@@ -202,7 +204,7 @@ USAGE
|
||||
$ eas device:create
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -216,7 +218,7 @@ OPTIONS
|
||||
--apple-team-id=apple-team-id
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -227,7 +229,7 @@ USAGE
|
||||
$ eas device:view [UDID]
|
||||
```
|
||||
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -246,6 +248,31 @@ OPTIONS
|
||||
|
||||
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
information about the current project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:info
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
create or link an EAS project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas project:init
|
||||
|
||||
ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas secret:create`
|
||||
|
||||
Create an environment secret on the current project or owner account.
|
||||
@@ -255,12 +282,13 @@ USAGE
|
||||
$ eas secret:create
|
||||
|
||||
OPTIONS
|
||||
--force Delete and recreate existing secrets
|
||||
--name=name Name of the secret
|
||||
--scope=(account|project) [default: project] Scope for the secret
|
||||
--value=value Value of the secret
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
_See code: [src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/secret/create.ts)_
|
||||
|
||||
## `eas secret:delete`
|
||||
|
||||
@@ -277,7 +305,7 @@ DESCRIPTION
|
||||
Unsure where to find the secret's ID? Run eas secrets:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
_See code: [src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/secret/delete.ts)_
|
||||
|
||||
## `eas secret:list`
|
||||
|
||||
@@ -288,7 +316,7 @@ USAGE
|
||||
$ eas secret:list
|
||||
```
|
||||
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
_See code: [src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/secret/list.ts)_
|
||||
|
||||
## `eas submit --platform=(android|ios)`
|
||||
|
||||
@@ -314,7 +342,8 @@ OPTIONS
|
||||
|
||||
iOS specific options=asc-app-id App Store Connect unique application Apple ID
|
||||
number. Providing this param results in skipping
|
||||
app creation step. Learn more.
|
||||
app creation step. Learn more:
|
||||
https://expo.fyi/asc-app-id
|
||||
|
||||
iOS specific options=bundle-identifier Your iOS Bundle Identifier (default:
|
||||
expo.ios.bundleIdentifier from app config)
|
||||
@@ -367,7 +396,7 @@ EXAMPLES
|
||||
and provide its App ID
|
||||
```
|
||||
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -386,7 +415,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -400,7 +429,7 @@ ARGUMENTS
|
||||
ID ID of the webhook to delete
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -414,7 +443,7 @@ OPTIONS
|
||||
--event=(BUILD) Event type that triggers the webhook
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -434,7 +463,7 @@ OPTIONS
|
||||
--url=url Webhook URL
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -448,5 +477,5 @@ ARGUMENTS
|
||||
ID ID of the webhook to view
|
||||
```
|
||||
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.18.2/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.22.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
@@ -1,2 +1,9 @@
|
||||
import { fs } from 'memfs';
|
||||
|
||||
// needed because of a weird bug with tempy (dependency of @expo/config-plugins)
|
||||
fs.mkdirSync('/tmp');
|
||||
if (process.env.TMPDIR) {
|
||||
fs.mkdirSync(process.env.TMPDIR, { recursive: true });
|
||||
}
|
||||
|
||||
module.exports = fs;
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
};
|
||||
Generated
+1608
-982
File diff suppressed because it is too large.
Load diff
@@ -2,5 +2,6 @@ module.exports = {
|
||||
...require('../../jest/jest.config.js'),
|
||||
rootDir: __dirname,
|
||||
roots: ['src', '__mocks__'],
|
||||
globalSetup: './global-setup.ts',
|
||||
globalSetup: './jest/global-setup.ts',
|
||||
setupFilesAfterEnv: ['./jest/setup-after-env.ts'],
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
process.env.EAS_NO_VCS = '1';
|
||||
process.env.EAS_PROJECT_ROOT = '/app';
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-console */
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
console.error = jest.fn();
|
||||
});
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"description": "EAS command line tool",
|
||||
"version": "0.18.2",
|
||||
"version": "0.22.0",
|
||||
"author": "Expo <[email protected]>",
|
||||
"bin": {
|
||||
"eas": "./bin/run"
|
||||
},
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"dependencies": {
|
||||
"@amplitude/identify": "1.5.0",
|
||||
"@amplitude/node": "1.5.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.20",
|
||||
"@amplitude/identify": "1.7.0",
|
||||
"@amplitude/node": "1.7.0",
|
||||
"@expo/apple-utils": "0.0.0-alpha.24",
|
||||
"@expo/config": "3.3.42",
|
||||
"@expo/config-plugins": "2.0.2",
|
||||
"@expo/eas-build-job": "0.2.37",
|
||||
"@expo/eas-json": "^0.18.2",
|
||||
"@expo/config-plugins": "3.0.3",
|
||||
"@expo/eas-build-job": "0.2.44",
|
||||
"@expo/eas-json": "^0.22.0",
|
||||
"@expo/json-file": "8.2.25",
|
||||
"@expo/pkcs12": "0.0.4",
|
||||
"@expo/plist": "0.0.11",
|
||||
@@ -131,15 +131,27 @@
|
||||
],
|
||||
"helpClass": "./build/help",
|
||||
"topics": {
|
||||
"account": {
|
||||
"description": "manage your account"
|
||||
},
|
||||
"build": {
|
||||
"description": "build app binaries"
|
||||
},
|
||||
"channel": {
|
||||
"description": "manage update channels"
|
||||
},
|
||||
"device": {
|
||||
"description": "manage your Apple devices for internal distribution"
|
||||
},
|
||||
"project": {
|
||||
"description": "manage your project"
|
||||
},
|
||||
"release": {
|
||||
"description": "manage update releases"
|
||||
},
|
||||
"secret": {
|
||||
"description": "manage project and account secrets"
|
||||
},
|
||||
"update": {
|
||||
"description": "manage individual updates"
|
||||
},
|
||||
|
||||
@@ -6,4 +6,5 @@ readmeContent = readmeContent.replace(/\[build\//g, '[src/');
|
||||
readmeContent = readmeContent.replace(/build\/commands/g, 'packages/eas-cli/src/commands');
|
||||
fs.writeFileSync('README.md', readmeContent);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Patched README path generation');
|
||||
@@ -36,7 +36,7 @@ export async function initAsync(): Promise<void> {
|
||||
process.env.EXPO_STAGING || process.env.EXPO_LOCAL
|
||||
? 'cdebbc678931403439486c4750781544'
|
||||
: '4ac443afd5073c0df6169291db1d3495';
|
||||
client = Amplitude.init(apiKey);
|
||||
client = Amplitude.init(apiKey, { retryClass: new Amplitude.OfflineRetryHandler(apiKey) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,14 +94,13 @@ export async function readChannelSafelyAsync(projectDir: string): Promise<string
|
||||
const androidManifest = await getAndroidManifestAsync(projectDir);
|
||||
const stringifiedRequestHeaders = AndroidConfig.Manifest.getMainApplicationMetaDataValue(
|
||||
androidManifest,
|
||||
'expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY' //TODO-JJ AndroidConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY once https://github.com/expo/expo-cli/pull/3571 is published
|
||||
AndroidConfig.Updates.Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY
|
||||
);
|
||||
if (!stringifiedRequestHeaders) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(stringifiedRequestHeaders)['expo-channel-name'] ?? null;
|
||||
} catch (err) {
|
||||
console.log({ err });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
||||
import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
|
||||
import { readVersionCode, readVersionName } from '../version';
|
||||
import { readVersionCodeAsync, readVersionNameAsync } from '../version';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
@@ -20,35 +20,35 @@ beforeEach(() => {
|
||||
fs.mkdirpSync(os.tmpdir());
|
||||
});
|
||||
|
||||
describe(readVersionCode, () => {
|
||||
describe(readVersionCodeAsync, () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the version code from native code', () => {
|
||||
it('reads the version code from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const versionCode = readVersionCode('/repo', exp);
|
||||
const versionCode = await readVersionCodeAsync('/repo', exp);
|
||||
expect(versionCode).toBe(123);
|
||||
});
|
||||
});
|
||||
describe('managed project', () => {
|
||||
it('reads the version code from expo config', () => {
|
||||
it('reads the version code from expo config', async () => {
|
||||
const exp = initManagedProject();
|
||||
const versionCode = readVersionCode('/repo', exp);
|
||||
const versionCode = await readVersionCodeAsync('/repo', exp);
|
||||
expect(versionCode).toBe(123);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(readVersionName, () => {
|
||||
describe(readVersionNameAsync, () => {
|
||||
describe('generic project', () => {
|
||||
it('reads the version name from native code', () => {
|
||||
it('reads the version name from native code', async () => {
|
||||
const exp = initGenericProject();
|
||||
const versionName = readVersionName('/repo', exp);
|
||||
const versionName = await readVersionNameAsync('/repo', exp);
|
||||
expect(versionName).toBe('1.0');
|
||||
});
|
||||
});
|
||||
describe('managed project', () => {
|
||||
it('reads the version from expo config', () => {
|
||||
it('reads the version from expo config', async () => {
|
||||
const exp = initManagedProject();
|
||||
const versionName = readVersionName('/repo', exp);
|
||||
const versionName = await readVersionNameAsync('/repo', exp);
|
||||
expect(versionName).toBe('1.0.0');
|
||||
});
|
||||
});
|
||||
@@ -72,6 +72,7 @@ function initGenericProject(): ExpoConfig {
|
||||
versionName "1.0"
|
||||
}
|
||||
}`,
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
'/repo'
|
||||
);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Android, Metadata, Workflow } from '@expo/eas-build-job';
|
||||
import { EasConfig } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import nullthrows from 'nullthrows';
|
||||
import path from 'path';
|
||||
|
||||
import AndroidCredentialsProvider, {
|
||||
AndroidCredentials,
|
||||
@@ -13,46 +11,31 @@ import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutatio
|
||||
import Log from '../../log';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
getApplicationId,
|
||||
getApplicationIdAsync,
|
||||
} from '../../project/android/applicationId';
|
||||
import { toggleConfirmAsync } from '../../prompts';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { CredentialsResult, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
import { BuildContext, CommandContext, createBuildContext } from '../context';
|
||||
import { BuildContext, CommandContext, createBuildContextAsync } from '../context';
|
||||
import { transformMetadata } from '../graphql';
|
||||
import { Platform } from '../types';
|
||||
import { logCredentialsSource } from '../utils/credentials';
|
||||
import { validateAndSyncProjectConfigurationAsync } from './configure';
|
||||
import { transformGenericJob, transformManagedJob } from './graphql';
|
||||
import { transformJob } from './graphql';
|
||||
import { prepareJobAsync } from './prepareJob';
|
||||
|
||||
export async function prepareAndroidBuildAsync(
|
||||
commandCtx: CommandContext,
|
||||
easConfig: EasConfig
|
||||
): Promise<() => Promise<string | undefined>> {
|
||||
const buildCtx = createBuildContext<Platform.ANDROID>({
|
||||
const buildCtx = await createBuildContextAsync<Platform.ANDROID>({
|
||||
commandCtx,
|
||||
platform: Platform.ANDROID,
|
||||
easConfig,
|
||||
});
|
||||
const { buildProfile } = buildCtx;
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
!(await fs.pathExists(path.join(commandCtx.projectDir, 'android')))
|
||||
) {
|
||||
throw new Error(
|
||||
`"android" directory not found. If you're trying to build a managed project, set ${chalk.bold(
|
||||
`builds.android.${commandCtx.profile}.workflow`
|
||||
)} in "eas.json" to "managed".`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
buildProfile.distribution === 'internal' &&
|
||||
buildProfile.gradleCommand?.match(/bundle/)
|
||||
) {
|
||||
if (buildProfile.distribution === 'internal' && buildProfile.gradleCommand?.match(/bundle/)) {
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn(
|
||||
`You're building your Android app for internal distribution. However, we've detected that the Gradle command you defined (${chalk.underline(
|
||||
@@ -68,7 +51,7 @@ This means that it will most likely produce an AAB and you will not be able to i
|
||||
}
|
||||
}
|
||||
|
||||
if (buildCtx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
if (buildCtx.workflow === Workflow.MANAGED) {
|
||||
await ensureApplicationIdIsDefinedForManagedProjectAsync(commandCtx.projectDir, commandCtx.exp);
|
||||
}
|
||||
|
||||
@@ -76,7 +59,7 @@ This means that it will most likely produce an AAB and you will not be able to i
|
||||
ctx: buildCtx,
|
||||
ensureCredentialsAsync: ensureAndroidCredentialsAsync,
|
||||
ensureProjectConfiguredAsync: async () => {
|
||||
if (buildCtx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
if (buildCtx.workflow === Workflow.GENERIC) {
|
||||
await validateAndSyncProjectConfigurationAsync(commandCtx.projectDir, commandCtx.exp);
|
||||
}
|
||||
},
|
||||
@@ -87,30 +70,18 @@ This means that it will most likely produce an AAB and you will not be able to i
|
||||
metadata: Metadata
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
if (job.type === Workflow.GENERIC) {
|
||||
const graphqlJob = transformGenericJob(job);
|
||||
return await BuildMutation.createAndroidGenericBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
} else {
|
||||
const graphqlJob = transformManagedJob(job);
|
||||
return await BuildMutation.createAndroidManagedBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
}
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createAndroidBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function shouldProvideCredentials(ctx: BuildContext<Platform.ANDROID>): boolean {
|
||||
return (
|
||||
ctx.buildProfile.workflow === Workflow.MANAGED ||
|
||||
(ctx.buildProfile.workflow === Workflow.GENERIC && !ctx.buildProfile.withoutCredentials)
|
||||
);
|
||||
return !ctx.buildProfile.withoutCredentials;
|
||||
}
|
||||
|
||||
async function ensureAndroidCredentialsAsync(
|
||||
@@ -119,7 +90,7 @@ async function ensureAndroidCredentialsAsync(
|
||||
if (!shouldProvideCredentials(ctx)) {
|
||||
return;
|
||||
}
|
||||
const androidApplicationIdentifier = getApplicationId(
|
||||
const androidApplicationIdentifier = await getApplicationIdAsync(
|
||||
ctx.commandCtx.projectDir,
|
||||
ctx.commandCtx.exp
|
||||
);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { Android } from '@expo/eas-build-job';
|
||||
|
||||
import {
|
||||
AndroidGenericJobInput,
|
||||
AndroidManagedBuildType,
|
||||
AndroidManagedJobInput,
|
||||
} from '../../graphql/generated';
|
||||
import { transformProjectArchive } from '../graphql';
|
||||
import { AndroidBuildType, AndroidJobInput } from '../../graphql/generated';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformGenericJob(job: Android.GenericJob): AndroidGenericJobInput {
|
||||
export function transformJob(job: Android.Job): AndroidJobInput {
|
||||
return {
|
||||
type: transformWorkflow(job.type),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
@@ -18,29 +15,17 @@ export function transformGenericJob(job: Android.GenericJob): AndroidGenericJobI
|
||||
cache: job.cache,
|
||||
gradleCommand: job.gradleCommand,
|
||||
artifactPath: job.artifactPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformManagedJob(job: Android.ManagedJob): AndroidManagedJobInput {
|
||||
return {
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
secrets: job.secrets,
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
username: job.username,
|
||||
buildType: transformBuildType(job.buildType),
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Android.ManagedBuildType): AndroidManagedBuildType {
|
||||
if (buildType === Android.ManagedBuildType.APK) {
|
||||
return AndroidManagedBuildType.Apk;
|
||||
} else if (buildType === Android.ManagedBuildType.APP_BUNDLE) {
|
||||
return AndroidManagedBuildType.AppBundle;
|
||||
function transformBuildType(buildType: Android.BuildType): AndroidBuildType {
|
||||
if (buildType === Android.BuildType.APK) {
|
||||
return AndroidBuildType.Apk;
|
||||
} else if (buildType === Android.BuildType.APP_BUNDLE) {
|
||||
return AndroidBuildType.AppBundle;
|
||||
} else {
|
||||
return AndroidManagedBuildType.DevelopmentClient;
|
||||
return AndroidBuildType.DevelopmentClient;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Android, ArchiveSource, Cache, Job, Workflow, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { AndroidGenericBuildProfile, AndroidManagedBuildProfile } from '@expo/eas-json';
|
||||
import { Android, ArchiveSource, Job, sanitizeJob } from '@expo/eas-build-job';
|
||||
import path from 'path';
|
||||
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
@@ -18,33 +17,9 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
if (ctx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
const partialJob = await prepareGenericJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else if (ctx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
const partialJob = await prepareManagedJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else {
|
||||
throw new Error("Unknown workflow. Shouldn't happen");
|
||||
}
|
||||
}
|
||||
|
||||
interface CommonJobProperties {
|
||||
platform: Platform.ANDROID;
|
||||
projectArchive: ArchiveSource;
|
||||
builderEnvironment: Android.BuilderEnvironment;
|
||||
cache: Cache;
|
||||
secrets: {
|
||||
buildCredentials?: {
|
||||
keystore: Android.Keystore;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareJobCommonAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Partial<CommonJobProperties>> {
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const { credentials } = jobData;
|
||||
const buildCredentials = credentials
|
||||
? {
|
||||
@@ -59,8 +34,16 @@ async function prepareJobCommonAsync(
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
const { gradleCommand } = ctx.buildProfile;
|
||||
let buildType: Android.BuildType | undefined = ctx.buildProfile.buildType;
|
||||
if (!buildType && !gradleCommand && ctx.buildProfile.distribution === 'internal') {
|
||||
buildType = Android.BuildType.APK;
|
||||
}
|
||||
|
||||
const job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.ANDROID,
|
||||
projectRootDirectory,
|
||||
projectArchive: jobData.projectArchive,
|
||||
builderEnvironment: {
|
||||
image: ctx.buildProfile.image,
|
||||
@@ -77,42 +60,15 @@ async function prepareJobCommonAsync(
|
||||
secrets: {
|
||||
...buildCredentials,
|
||||
},
|
||||
};
|
||||
}
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
|
||||
async function prepareGenericJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData,
|
||||
buildProfile: AndroidGenericBuildProfile
|
||||
): Promise<Partial<Android.GenericJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, jobData)),
|
||||
type: Workflow.GENERIC,
|
||||
gradleCommand: buildProfile.gradleCommand,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
gradleCommand,
|
||||
artifactPath: ctx.buildProfile.artifactPath,
|
||||
|
||||
async function prepareManagedJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData,
|
||||
buildProfile: AndroidManagedBuildProfile
|
||||
): Promise<Partial<Android.ManagedJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, jobData)),
|
||||
type: Workflow.MANAGED,
|
||||
username,
|
||||
buildType: buildProfile.buildType,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
buildType,
|
||||
};
|
||||
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
@@ -3,10 +3,13 @@ import { AndroidConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { resolveWorkflow } from '../../project/workflow';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
|
||||
export function readVersionCode(projectDir: string, exp: ExpoConfig): number | undefined {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
export async function readVersionCodeAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<number | undefined> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = readBuildGradle(projectDir);
|
||||
const matchResult = buildGradle?.match(/versionCode (.*)/);
|
||||
@@ -16,12 +19,15 @@ export function readVersionCode(projectDir: string, exp: ExpoConfig): number | u
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
return AndroidConfig.Version.getVersionCode(exp) ?? undefined;
|
||||
return AndroidConfig.Version.getVersionCode(exp) ?? 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function readVersionName(projectDir: string, exp: ExpoConfig): string | undefined {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
export async function readVersionNameAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<string | undefined> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const buildGradle = readBuildGradle(projectDir);
|
||||
const matchResult = buildGradle?.match(/versionName ["'](.*)["']/);
|
||||
|
||||
@@ -102,6 +102,20 @@ export async function prepareBuildRequestForPlatformAsync<
|
||||
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'
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import Log from '../log';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { promptAsync } from '../prompts';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
@@ -37,8 +39,10 @@ export async function configureAsync(options: {
|
||||
options.platform
|
||||
),
|
||||
shouldConfigureIos: [RequestedPlatform.All, RequestedPlatform.Ios].includes(options.platform),
|
||||
hasAndroidNativeProject: await fs.pathExists(path.join(options.projectDir, 'android')),
|
||||
hasIosNativeProject: await fs.pathExists(path.join(options.projectDir, 'ios')),
|
||||
hasAndroidNativeProject:
|
||||
(await resolveWorkflowAsync(options.projectDir, Platform.ANDROID)) === Workflow.GENERIC,
|
||||
hasIosNativeProject:
|
||||
(await resolveWorkflowAsync(options.projectDir, Platform.IOS)) === Workflow.GENERIC,
|
||||
};
|
||||
|
||||
Log.newLine();
|
||||
@@ -59,32 +63,40 @@ export async function configureAsync(options: {
|
||||
}
|
||||
}
|
||||
|
||||
const MANAGED_DEFAULTS = {
|
||||
const ANDROID_MANAGED_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'managed',
|
||||
buildType: 'app-bundle',
|
||||
},
|
||||
development: {
|
||||
workflow: 'managed',
|
||||
buildType: 'development-client',
|
||||
distribution: 'internal',
|
||||
},
|
||||
};
|
||||
const ANDROID_GENERIC_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'generic',
|
||||
gradleCommand: ':app:bundleRelease',
|
||||
},
|
||||
development: {
|
||||
workflow: 'generic',
|
||||
gradleCommand: ':app:assembleDebug',
|
||||
distribution: 'internal',
|
||||
},
|
||||
};
|
||||
const IOS_GENERIC_DEFAULTS = {
|
||||
|
||||
const IOS_MANAGED_DEFAULTS = {
|
||||
release: {
|
||||
workflow: 'generic',
|
||||
buildType: 'release',
|
||||
},
|
||||
development: {
|
||||
buildType: 'development-client',
|
||||
distribution: 'internal',
|
||||
},
|
||||
};
|
||||
|
||||
const IOS_GENERIC_DEFAULTS = {
|
||||
release: {
|
||||
schemeBuildConfiguration: 'Release',
|
||||
},
|
||||
development: {
|
||||
workflow: 'generic',
|
||||
schemeBuildConfiguration: 'Debug',
|
||||
distribution: 'internal',
|
||||
},
|
||||
@@ -121,15 +133,17 @@ export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<v
|
||||
|
||||
const easJson = {
|
||||
builds: {
|
||||
...existingEasJson,
|
||||
...existingEasJson?.builds,
|
||||
...(shouldInitAndroid
|
||||
? {
|
||||
android: ctx.hasAndroidNativeProject ? ANDROID_GENERIC_DEFAULTS : MANAGED_DEFAULTS,
|
||||
android: ctx.hasAndroidNativeProject
|
||||
? ANDROID_GENERIC_DEFAULTS
|
||||
: ANDROID_MANAGED_DEFAULTS,
|
||||
}
|
||||
: null),
|
||||
...(shouldInitIOS
|
||||
? {
|
||||
ios: ctx.hasIosNativeProject ? IOS_GENERIC_DEFAULTS : MANAGED_DEFAULTS,
|
||||
ios: ctx.hasIosNativeProject ? IOS_GENERIC_DEFAULTS : IOS_MANAGED_DEFAULTS,
|
||||
}
|
||||
: null),
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import resolveFrom from 'resolve-from';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getProjectAccountName } from '../project/projectUtils';
|
||||
import { resolveWorkflowAsync } from '../project/workflow';
|
||||
import { findAccountByName } from '../user/Account';
|
||||
import { Actor } from '../user/User';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
@@ -92,9 +93,10 @@ export interface BuildContext<T extends Platform> {
|
||||
trackingCtx: TrackingContext;
|
||||
platform: T;
|
||||
buildProfile: PlatformBuildProfile<T>;
|
||||
workflow: Workflow;
|
||||
}
|
||||
|
||||
export function createBuildContext<T extends Platform>({
|
||||
export async function createBuildContextAsync<T extends Platform>({
|
||||
platform,
|
||||
easConfig,
|
||||
commandCtx,
|
||||
@@ -102,45 +104,58 @@ export function createBuildContext<T extends Platform>({
|
||||
platform: T;
|
||||
easConfig: EasConfig;
|
||||
commandCtx: CommandContext;
|
||||
}): BuildContext<T> {
|
||||
}): Promise<BuildContext<T>> {
|
||||
const buildProfile = easConfig.builds[platform] as PlatformBuildProfile<T> | undefined;
|
||||
if (!buildProfile) {
|
||||
throw new Error(`${platform} build profile does not exist`);
|
||||
}
|
||||
|
||||
const workflow = await resolveWorkflowAsync(commandCtx.projectDir, platform);
|
||||
const accountId = findAccountByName(commandCtx.user.accounts, commandCtx.accountName)?.id;
|
||||
const devClientProperties = getDevClientEventProperties(platform, commandCtx, buildProfile);
|
||||
const devClientProperties = getDevClientEventProperties({
|
||||
platform,
|
||||
projectDir: commandCtx.projectDir,
|
||||
buildProfile,
|
||||
workflow,
|
||||
});
|
||||
const trackingCtx = {
|
||||
tracking_id: uuidv4(),
|
||||
platform,
|
||||
...(accountId && { account_id: accountId }),
|
||||
account_name: commandCtx.accountName,
|
||||
project_id: commandCtx.projectId,
|
||||
project_type: buildProfile.workflow,
|
||||
project_type: workflow,
|
||||
...devClientProperties,
|
||||
};
|
||||
Analytics.logEvent(Event.BUILD_COMMAND, trackingCtx);
|
||||
return {
|
||||
commandCtx,
|
||||
workflow,
|
||||
trackingCtx,
|
||||
platform,
|
||||
buildProfile,
|
||||
};
|
||||
}
|
||||
|
||||
function getDevClientEventProperties(
|
||||
platform: Platform,
|
||||
commandCtx: CommandContext,
|
||||
buildProfile: AndroidBuildProfile | IosBuildProfile
|
||||
): Partial<TrackingContext> {
|
||||
function getDevClientEventProperties({
|
||||
platform,
|
||||
projectDir,
|
||||
buildProfile,
|
||||
workflow,
|
||||
}: {
|
||||
platform: Platform;
|
||||
projectDir: string;
|
||||
buildProfile: AndroidBuildProfile | IosBuildProfile;
|
||||
workflow: Workflow;
|
||||
}): Partial<TrackingContext> {
|
||||
let includesDevClient;
|
||||
const version = tryGetDevClientVersion(commandCtx.projectDir);
|
||||
if (buildProfile.workflow === Workflow.MANAGED) {
|
||||
includesDevClient = buildProfile.buildType === 'development-client';
|
||||
} else if (platform === Platform.ANDROID && 'gradleCommand' in buildProfile) {
|
||||
const version = tryGetDevClientVersion(projectDir);
|
||||
if (platform === Platform.ANDROID && 'gradleCommand' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.gradleCommand?.includes('Debug'));
|
||||
} else if (platform === Platform.IOS && 'schemeBuildConfiguration' in buildProfile) {
|
||||
includesDevClient = Boolean(version && buildProfile.schemeBuildConfiguration === 'Debug');
|
||||
} else if ('buildType' in buildProfile) {
|
||||
includesDevClient = buildProfile.buildType === 'development-client';
|
||||
} else {
|
||||
includesDevClient = false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ArchiveSource, ArchiveSourceType, Metadata, Workflow } from '@expo/eas-
|
||||
|
||||
import {
|
||||
BuildCredentialsSource,
|
||||
BuildIosEnterpriseProvisioning,
|
||||
BuildMetadataInput,
|
||||
BuildWorkflow,
|
||||
DistributionType,
|
||||
@@ -32,6 +33,9 @@ export function transformMetadata(metadata: Metadata): BuildMetadataInput {
|
||||
metadata.credentialsSource && transformCredentialsSource(metadata.credentialsSource),
|
||||
distribution: metadata.distribution && transformDistribution(metadata.distribution),
|
||||
workflow: metadata.workflow && transformWorkflow(metadata.workflow),
|
||||
iosEnterpriseProvisioning:
|
||||
metadata.iosEnterpriseProvisioning &&
|
||||
transformIosEnterpriseProvisioning(metadata.iosEnterpriseProvisioning),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,10 +59,20 @@ function transformDistribution(distribution: Metadata['distribution']): Distribu
|
||||
}
|
||||
}
|
||||
|
||||
function transformWorkflow(workflow: Metadata['workflow']): BuildWorkflow {
|
||||
export function transformWorkflow(workflow: Workflow): BuildWorkflow {
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
return BuildWorkflow.Generic;
|
||||
} else {
|
||||
return BuildWorkflow.Managed;
|
||||
}
|
||||
}
|
||||
|
||||
export function transformIosEnterpriseProvisioning(
|
||||
enterpriseProvisioning: Metadata['iosEnterpriseProvisioning']
|
||||
): BuildIosEnterpriseProvisioning {
|
||||
if (enterpriseProvisioning === 'adhoc') {
|
||||
return BuildIosEnterpriseProvisioning.Adhoc;
|
||||
} else {
|
||||
return BuildIosEnterpriseProvisioning.Universal;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ async function ensureUpdatesConfiguredAsync(projectDir: string, exp: ExpoConfig)
|
||||
|
||||
async function readExpoPlistAsync(projectDir: string): Promise<IOSConfig.ExpoPlist> {
|
||||
const expoPlistPath = IOSConfig.Paths.getExpoPlistPath(projectDir);
|
||||
return await readPlistAsync(expoPlistPath);
|
||||
return (await readPlistAsync(expoPlistPath)) as IOSConfig.ExpoPlist;
|
||||
}
|
||||
|
||||
async function writeExpoPlistAsync(
|
||||
|
||||
@@ -15,12 +15,7 @@ import {
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
// do not remove the following line
|
||||
// this fixes a weird error with tempy in @expo/image-utils
|
||||
fs.removeSync(os.tmpdir());
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { Ios, Job, Metadata, Workflow } from '@expo/eas-build-job';
|
||||
import { EasConfig } from '@expo/eas-json';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import { IosCredentials } from '../../credentials/ios/types';
|
||||
import { BuildMutation, BuildResult } from '../../graphql/mutations/BuildMutation';
|
||||
@@ -10,37 +7,26 @@ import { ensureBundleIdentifierIsDefinedForManagedProjectAsync } from '../../pro
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { JobData, prepareBuildRequestForPlatformAsync } from '../build';
|
||||
import { BuildContext, CommandContext, createBuildContext } from '../context';
|
||||
import { BuildContext, CommandContext, createBuildContextAsync } from '../context';
|
||||
import { transformMetadata } from '../graphql';
|
||||
import { Platform } from '../types';
|
||||
import { validateAndSyncProjectConfigurationAsync } from './configure';
|
||||
import { ensureIosCredentialsAsync } from './credentials';
|
||||
import { transformGenericJob, transformManagedJob } from './graphql';
|
||||
import { transformJob } from './graphql';
|
||||
import { prepareJobAsync } from './prepareJob';
|
||||
|
||||
export async function prepareIosBuildAsync(
|
||||
commandCtx: CommandContext,
|
||||
easConfig: EasConfig
|
||||
): Promise<() => Promise<string | undefined>> {
|
||||
const buildCtx = createBuildContext<Platform.IOS>({
|
||||
const buildCtx = await createBuildContextAsync<Platform.IOS>({
|
||||
commandCtx,
|
||||
platform: Platform.IOS,
|
||||
easConfig,
|
||||
});
|
||||
const { buildProfile } = buildCtx;
|
||||
|
||||
if (
|
||||
buildProfile.workflow === Workflow.GENERIC &&
|
||||
!(await fs.pathExists(path.join(commandCtx.projectDir, 'ios')))
|
||||
) {
|
||||
throw new Error(
|
||||
`"ios" directory not found. If you're trying to build a managed project, set ${chalk.bold(
|
||||
`builds.ios.${commandCtx.profile}.workflow`
|
||||
)} in "eas.json" to "managed".`
|
||||
);
|
||||
}
|
||||
|
||||
if (buildProfile.workflow === Workflow.MANAGED) {
|
||||
if (buildCtx.workflow === Workflow.MANAGED) {
|
||||
await ensureBundleIdentifierIsDefinedForManagedProjectAsync(
|
||||
commandCtx.projectDir,
|
||||
commandCtx.exp
|
||||
@@ -87,21 +73,12 @@ export async function prepareIosBuildAsync(
|
||||
metadata: Metadata
|
||||
): Promise<BuildResult> => {
|
||||
const graphqlMetadata = transformMetadata(metadata);
|
||||
if (job.type === Workflow.GENERIC) {
|
||||
const graphqlJob = transformGenericJob(job);
|
||||
return await BuildMutation.createIosGenericBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
} else {
|
||||
const graphqlJob = transformManagedJob(job);
|
||||
return await BuildMutation.createIosManagedBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
}
|
||||
const graphqlJob = transformJob(job);
|
||||
return await BuildMutation.createIosBuildAsync({
|
||||
appId,
|
||||
job: graphqlJob,
|
||||
metadata: graphqlMetadata,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpoConfig } from '@expo/config';
|
||||
import { Workflow } from '@expo/eas-build-job';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { IosBuildProfile, VersionAutoIncrement } from '@expo/eas-json';
|
||||
|
||||
import Log from '../../log';
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
|
||||
warnIfBundleIdentifierDefinedInAppConfigForGenericProject,
|
||||
} from '../../project/ios/bundleIdentifier';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { ConfigureContext } from '../context';
|
||||
import { isExpoUpdatesInstalled } from '../utils/updates';
|
||||
import { configureUpdatesAsync, syncUpdatesConfigurationAsync } from './UpdatesModule';
|
||||
@@ -35,7 +36,8 @@ export async function validateAndSyncProjectConfigurationAsync({
|
||||
exp: ExpoConfig;
|
||||
buildProfile: IosBuildProfile;
|
||||
}): Promise<void> {
|
||||
const { workflow, autoIncrement } = buildProfile;
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
const { autoIncrement } = buildProfile;
|
||||
const versionBumpStrategy = resolveVersionBumpStrategy(autoIncrement);
|
||||
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function ensureIosCredentialsAsync(
|
||||
iosCapabilitiesOptions: {
|
||||
entitlements: await resolveEntitlementsJsonAsync(
|
||||
buildCtx.commandCtx.projectDir,
|
||||
buildCtx.buildProfile.workflow
|
||||
buildCtx.workflow
|
||||
),
|
||||
},
|
||||
distribution: buildCtx.buildProfile.distribution ?? 'store',
|
||||
|
||||
@@ -3,15 +3,15 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
DistributionType,
|
||||
IosGenericJobInput,
|
||||
IosBuildType,
|
||||
IosJobInput,
|
||||
IosJobSecretsInput,
|
||||
IosManagedBuildType,
|
||||
IosManagedJobInput,
|
||||
} from '../../graphql/generated';
|
||||
import { transformProjectArchive } from '../graphql';
|
||||
import { transformProjectArchive, transformWorkflow } from '../graphql';
|
||||
|
||||
export function transformGenericJob(job: Ios.GenericJob): IosGenericJobInput {
|
||||
export function transformJob(job: Ios.Job): IosJobInput {
|
||||
return {
|
||||
type: transformWorkflow(job.type),
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
@@ -20,22 +20,11 @@ export function transformGenericJob(job: Ios.GenericJob): IosGenericJobInput {
|
||||
secrets: transformIosSecrets(job.secrets),
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
|
||||
scheme: job.scheme,
|
||||
buildConfiguration: job.buildConfiguration,
|
||||
artifactPath: job.artifactPath,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformManagedJob(job: Ios.ManagedJob): IosManagedJobInput {
|
||||
return {
|
||||
projectArchive: transformProjectArchive(job.projectArchive),
|
||||
projectRootDirectory: job.projectRootDirectory,
|
||||
releaseChannel: job.releaseChannel,
|
||||
updates: job.updates,
|
||||
distribution: job.distribution && transformDistributionType(job.distribution),
|
||||
secrets: transformIosSecrets(job.secrets),
|
||||
builderEnvironment: job.builderEnvironment,
|
||||
cache: job.cache,
|
||||
buildType: job.buildType && transformBuildType(job.buildType),
|
||||
username: job.username,
|
||||
};
|
||||
@@ -73,10 +62,10 @@ function transformIosSecrets(secrets: {
|
||||
};
|
||||
}
|
||||
|
||||
function transformBuildType(buildType: Ios.ManagedBuildType): IosManagedBuildType {
|
||||
if (buildType === Ios.ManagedBuildType.DEVELOPMENT_CLIENT) {
|
||||
return IosManagedBuildType.DevelopmentClient;
|
||||
function transformBuildType(buildType: Ios.BuildType): IosBuildType {
|
||||
if (buildType === Ios.BuildType.DEVELOPMENT_CLIENT) {
|
||||
return IosBuildType.DevelopmentClient;
|
||||
} else {
|
||||
return IosManagedBuildType.Release;
|
||||
return IosBuildType.Release;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ArchiveSource, Cache, Ios, Job, Workflow, sanitizeJob } from '@expo/eas-build-job';
|
||||
import { IosGenericBuildProfile, IosManagedBuildProfile } from '@expo/eas-json';
|
||||
import { ArchiveSource, Ios, Job, sanitizeJob } from '@expo/eas-build-job';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
|
||||
@@ -20,44 +19,22 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData
|
||||
): Promise<Job> {
|
||||
if (ctx.buildProfile.workflow === Workflow.GENERIC) {
|
||||
const partialJob = await prepareGenericJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else if (ctx.buildProfile.workflow === Workflow.MANAGED) {
|
||||
const partialJob = await prepareManagedJobAsync(ctx, jobData, ctx.buildProfile);
|
||||
return sanitizeJob(partialJob);
|
||||
} else {
|
||||
throw new Error("Unknown workflow. Shouldn't happen");
|
||||
}
|
||||
}
|
||||
|
||||
interface CommonJobProperties {
|
||||
platform: Platform.IOS;
|
||||
projectArchive: ArchiveSource;
|
||||
builderEnvironment: Ios.BuilderEnvironment;
|
||||
distribution?: Ios.DistributionType;
|
||||
cache: Cache;
|
||||
secrets: {
|
||||
buildCredentials: Ios.BuildCredentials;
|
||||
environmentSecrets?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareJobCommonAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
{ credentials, projectArchive }: { credentials?: IosCredentials; projectArchive: ArchiveSource }
|
||||
): Promise<Partial<CommonJobProperties>> {
|
||||
const buildCredentials: CommonJobProperties['secrets']['buildCredentials'] = {};
|
||||
if (credentials) {
|
||||
const targetNames = Object.keys(credentials);
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
const buildCredentials: Ios.Job['secrets']['buildCredentials'] = {};
|
||||
if (jobData.credentials) {
|
||||
const targetNames = Object.keys(jobData.credentials);
|
||||
for (const targetName of targetNames) {
|
||||
buildCredentials[targetName] = prepareTargetCredentials(credentials[targetName]);
|
||||
buildCredentials[targetName] = prepareTargetCredentials(jobData.credentials[targetName]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const job = {
|
||||
type: ctx.workflow,
|
||||
platform: Platform.IOS,
|
||||
projectArchive,
|
||||
projectArchive: jobData.projectArchive,
|
||||
projectRootDirectory,
|
||||
distribution: ctx.buildProfile.distribution,
|
||||
builderEnvironment: {
|
||||
image: resolveImage(ctx),
|
||||
@@ -76,7 +53,18 @@ async function prepareJobCommonAsync(
|
||||
secrets: {
|
||||
buildCredentials,
|
||||
},
|
||||
releaseChannel: ctx.buildProfile.releaseChannel,
|
||||
updates: { channel: ctx.buildProfile.channel },
|
||||
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: ctx.buildProfile.schemeBuildConfiguration,
|
||||
artifactPath: ctx.buildProfile.artifactPath,
|
||||
|
||||
buildType: ctx.buildProfile.buildType,
|
||||
username,
|
||||
};
|
||||
|
||||
return sanitizeJob(job);
|
||||
}
|
||||
|
||||
function resolveImage(ctx: BuildContext<Platform.IOS>): Ios.BuilderEnvironment['image'] {
|
||||
@@ -99,47 +87,3 @@ function prepareTargetCredentials(targetCredentials: TargetCredentials): Ios.Tar
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareGenericJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData,
|
||||
buildProfile: IosGenericBuildProfile
|
||||
): Promise<Partial<Ios.GenericJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, {
|
||||
credentials: jobData.credentials,
|
||||
projectArchive: jobData.projectArchive,
|
||||
})),
|
||||
type: Workflow.GENERIC,
|
||||
scheme: jobData.buildScheme,
|
||||
buildConfiguration: buildProfile.schemeBuildConfiguration,
|
||||
artifactPath: buildProfile.artifactPath,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
async function prepareManagedJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData,
|
||||
buildProfile: IosManagedBuildProfile
|
||||
): Promise<Partial<Ios.ManagedJob>> {
|
||||
const projectRootDirectory =
|
||||
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
|
||||
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
|
||||
return {
|
||||
...(await prepareJobCommonAsync(ctx, {
|
||||
credentials: jobData.credentials,
|
||||
projectArchive: jobData.projectArchive,
|
||||
})),
|
||||
type: Workflow.MANAGED,
|
||||
buildType: buildProfile.buildType,
|
||||
username,
|
||||
releaseChannel: buildProfile.releaseChannel,
|
||||
updates: { channel: buildProfile.channel },
|
||||
projectRootDirectory,
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import nullthrows from 'nullthrows';
|
||||
import semver from 'semver';
|
||||
|
||||
import Log from '../../log';
|
||||
import { resolveWorkflow } from '../../project/workflow';
|
||||
import { resolveWorkflowAsync } from '../../project/workflow';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { updateAppJsonConfigAsync } from '../utils/appJson';
|
||||
import { readPlistAsync, writePlistAsync } from './plist';
|
||||
@@ -106,7 +106,7 @@ export async function readShortVersionAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<string | undefined> {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.IOS);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
return infoPlist.CFBundleShortVersionString;
|
||||
@@ -119,7 +119,7 @@ export async function readBuildNumberAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<string | undefined> {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.IOS);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
const infoPlist = await readInfoPlistAsync(projectDir);
|
||||
return infoPlist.CFBundleVersion;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Metadata } from '@expo/eas-build-job';
|
||||
import { CredentialsSource } from '@expo/eas-json';
|
||||
import { CredentialsSource, IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
|
||||
import { getApplicationId } from '../project/android/applicationId';
|
||||
import { getBundleIdentifier } from '../project/ios/bundleIdentifier';
|
||||
import { getApplicationIdAsync } from '../project/android/applicationId';
|
||||
import { getBundleIdentifierAsync } from '../project/ios/bundleIdentifier';
|
||||
import { getUsername } from '../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../user/actions';
|
||||
import vcs from '../vcs';
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
readChannelSafelyAsync as readAndroidChannelSafelyAsync,
|
||||
readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync,
|
||||
} from './android/UpdatesModule';
|
||||
import { readVersionCode, readVersionName } from './android/version';
|
||||
import { readVersionCodeAsync, readVersionNameAsync } from './android/version';
|
||||
import { BuildContext } from './context';
|
||||
import {
|
||||
readChannelSafelyAsync as readIosChannelSafelyAsync,
|
||||
@@ -37,23 +37,26 @@ export async function collectMetadata<T extends Platform>(
|
||||
): Promise<Metadata> {
|
||||
const channelOrReleaseChannel = await resolveChannelOrReleaseChannelAsync(ctx);
|
||||
return {
|
||||
// TODO: type error fixed in https://github.com/expo/eas-build/pull/34
|
||||
// remove @ts-expect-error after upgrading @expo/eas-build-job
|
||||
// @ts-expect-error error TS2322: not assignable to type 'Record<string, string | number>'
|
||||
trackingContext: ctx.trackingCtx,
|
||||
appVersion: await resolveAppVersionAsync(ctx),
|
||||
appBuildVersion: await resolveAppBuildVersionAsync(ctx),
|
||||
cliVersion: packageJSON.version,
|
||||
workflow: ctx.buildProfile.workflow,
|
||||
workflow: ctx.workflow,
|
||||
credentialsSource,
|
||||
sdkVersion: ctx.commandCtx.exp.sdkVersion,
|
||||
runtimeVersion: ctx.commandCtx.exp.runtimeVersion,
|
||||
...channelOrReleaseChannel,
|
||||
distribution: ctx.buildProfile.distribution ?? 'store',
|
||||
appName: ctx.commandCtx.exp.name,
|
||||
appIdentifier: resolveAppIdentifier(ctx),
|
||||
appIdentifier: await resolveAppIdentifierAsync(ctx),
|
||||
buildProfile: ctx.commandCtx.profile,
|
||||
gitCommitHash: await vcs.getCommitHashAsync(),
|
||||
username: getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync()),
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
ctx as BuildContext<Platform.IOS>
|
||||
),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,7 +66,7 @@ async function resolveAppVersionAsync<T extends Platform>(
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return await readShortVersionAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
} else {
|
||||
return readVersionName(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return await readVersionNameAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,16 +76,18 @@ async function resolveAppBuildVersionAsync<T extends Platform>(
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return await readBuildNumberAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
} else {
|
||||
const versionCode = readVersionCode(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
const versionCode = await readVersionCodeAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return versionCode !== undefined ? String(versionCode) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAppIdentifier<T extends Platform>(ctx: BuildContext<T>): string {
|
||||
async function resolveAppIdentifierAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>
|
||||
): Promise<string> {
|
||||
if (ctx.platform === Platform.IOS) {
|
||||
return getBundleIdentifier(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return await getBundleIdentifierAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
} else {
|
||||
return getApplicationId(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
return await getApplicationIdAsync(ctx.commandCtx.projectDir, ctx.commandCtx.exp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,3 +140,9 @@ async function getNativeChannelAsync<T extends Platform>(
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveIosEnterpriseProvisioning(
|
||||
ctx: BuildContext<Platform.IOS>
|
||||
): IosEnterpriseProvisioning | undefined {
|
||||
return ctx.buildProfile.enterpriseProvisioning;
|
||||
}
|
||||
@@ -7,9 +7,10 @@ import { requestedPlatformDisplayNames } from '../constants';
|
||||
|
||||
export function logCredentialsSource(credentialsSource: CredentialsSource, platform: Platform) {
|
||||
let message = `Using ${credentialsSource} ${requestedPlatformDisplayNames[platform]} credentials`;
|
||||
if (credentialsSource === CredentialsSource.LOCAL)
|
||||
if (credentialsSource === CredentialsSource.LOCAL) {
|
||||
message += ` ${chalk.dim('(credentials.json)')}`;
|
||||
else if (credentialsSource === CredentialsSource.REMOTE)
|
||||
} else if (credentialsSource === CredentialsSource.REMOTE) {
|
||||
message += ` ${chalk.dim('(Expo server)')}`;
|
||||
}
|
||||
Log.succeed(message);
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { BuildFragment, BuildStatus as GraphQLBuildStatus } from '../../graphql/generated';
|
||||
import {
|
||||
AppPlatform,
|
||||
BuildFragment,
|
||||
BuildStatus as GraphQLBuildStatus,
|
||||
} from '../../graphql/generated';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { appPlatformDisplayNames } from '../constants';
|
||||
import { getBuildLogsUrl } from './url';
|
||||
@@ -8,7 +12,7 @@ import { getBuildLogsUrl } from './url';
|
||||
export function formatGraphQLBuild(build: BuildFragment) {
|
||||
const actor = getActorName(build);
|
||||
const account = build.project.__typename === 'App' ? build.project.ownerAccount.name : 'unknown';
|
||||
const fields: { label: string; value: string }[] = [
|
||||
const fields: { label: string; value?: string | null }[] = [
|
||||
{ label: 'ID', value: build.id },
|
||||
{
|
||||
label: 'Platform',
|
||||
@@ -37,11 +41,39 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
},
|
||||
{
|
||||
label: 'Distribution',
|
||||
value: build.distribution?.toLowerCase() ?? chalk.gray('unknown'),
|
||||
value: build.distribution?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Enterprise Provisioning',
|
||||
value: build.iosEnterpriseProvisioning?.toLowerCase(),
|
||||
},
|
||||
{
|
||||
label: 'Release Channel',
|
||||
value: build.releaseChannel ?? chalk.gray('unknown'),
|
||||
value: build.releaseChannel,
|
||||
},
|
||||
{
|
||||
label: 'Channel',
|
||||
value: build.channel,
|
||||
},
|
||||
{
|
||||
label: 'SDK Version',
|
||||
value: build.sdkVersion,
|
||||
},
|
||||
{
|
||||
label: 'Runtime Version',
|
||||
value: build.runtimeVersion,
|
||||
},
|
||||
{
|
||||
label: 'Version',
|
||||
value: build.appVersion,
|
||||
},
|
||||
{
|
||||
label: build.platform === AppPlatform.Android ? 'Version code' : 'Build number',
|
||||
value: build.appBuildVersion,
|
||||
},
|
||||
{
|
||||
label: 'Commit',
|
||||
value: build.gitCommitHash,
|
||||
},
|
||||
{
|
||||
label: 'Logs',
|
||||
@@ -57,13 +89,13 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
return '<in progress>';
|
||||
case GraphQLBuildStatus.Canceled:
|
||||
case GraphQLBuildStatus.Errored:
|
||||
return '---------';
|
||||
return null;
|
||||
case GraphQLBuildStatus.Finished: {
|
||||
const url = build.artifacts?.buildUrl;
|
||||
return url ? url : chalk.red('not found');
|
||||
}
|
||||
default:
|
||||
return 'unknown';
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -81,7 +113,11 @@ export function formatGraphQLBuild(build: BuildFragment) {
|
||||
{ label: 'Started by', value: actor ?? 'unknown' },
|
||||
];
|
||||
|
||||
return formatFields(fields);
|
||||
const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null) as {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
return formatFields(filteredFields);
|
||||
}
|
||||
|
||||
const getActorName = (build: BuildFragment): string => {
|
||||
|
||||
@@ -85,7 +85,7 @@ function printBuildResult(accountName: string, build: BuildFragment): void {
|
||||
account: accountName,
|
||||
});
|
||||
const installUrl = getInternalDistributionInstallUrl(build);
|
||||
qrcodeTerminal.generate(installUrl, code => console.log(`${indentString(code, 2)}\n`));
|
||||
qrcodeTerminal.generate(installUrl, code => Log.log(`${indentString(code, 2)}\n`));
|
||||
Log.log(
|
||||
`${appPlatformEmojis[build.platform]} Open this link on your ${
|
||||
appPlatformDisplayNames[build.platform]
|
||||
|
||||
@@ -22,7 +22,7 @@ import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
async function getBranchInfoAsync({
|
||||
appId,
|
||||
name,
|
||||
}: GetBranchInfoQueryVariables): Promise<{ branchId: string }> {
|
||||
}: GetBranchInfoQueryVariables): Promise<GetBranchInfoQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<GetBranchInfoQuery, GetBranchInfoQueryVariables>(
|
||||
@@ -46,11 +46,7 @@ async function getBranchInfoAsync({
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
const branchId = data.app?.byId.updateBranchByName?.id;
|
||||
if (!branchId) {
|
||||
throw new Error(`Could not find branch ${name} on ${appId}`);
|
||||
}
|
||||
return { branchId };
|
||||
return data;
|
||||
}
|
||||
|
||||
async function deleteBranchOnAppAsync({
|
||||
@@ -122,7 +118,11 @@ export default class BranchDelete extends Command {
|
||||
}));
|
||||
}
|
||||
|
||||
const { branchId } = await getBranchInfoAsync({ appId: projectId, name });
|
||||
const data = await getBranchInfoAsync({ appId: projectId, name });
|
||||
const branchId = data.app?.byId.updateBranchByName?.id;
|
||||
if (!branchId) {
|
||||
throw new Error(`Could not find branch ${name} on ${fullName}`);
|
||||
}
|
||||
|
||||
if (!jsonFlag) {
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
@@ -100,6 +100,8 @@ export default class BranchList extends Command {
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Branches with their most recent update group:'));
|
||||
Log.log(table.toString());
|
||||
if (branches.length >= BRANCHES_LIMIT) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import Table from 'cli-table3';
|
||||
import dateFormat from 'dateformat';
|
||||
import gql from 'graphql-tag';
|
||||
import { uniqBy } from 'lodash';
|
||||
@@ -19,11 +18,7 @@ import {
|
||||
} from '../../graphql/generated';
|
||||
import { PublishMutation } from '../../graphql/mutations/PublishMutation';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import {
|
||||
PublishPlatform,
|
||||
buildBundlesAsync,
|
||||
@@ -33,6 +28,7 @@ import {
|
||||
} from '../../project/publish';
|
||||
import { promptAsync, selectAsync } from '../../prompts';
|
||||
import { formatUpdate } from '../../update/utils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import vcs from '../../vcs';
|
||||
import { listBranchesAsync } from './list';
|
||||
import { viewUpdateBranchAsync } from './view';
|
||||
@@ -134,9 +130,11 @@ export default class BranchPublish extends Command {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const accountName = await getProjectAccountNameAsync(exp);
|
||||
let { slug, runtimeVersion, sdkVersion } = exp;
|
||||
const { exp } = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
isPublicConfig: true,
|
||||
});
|
||||
let { runtimeVersion, sdkVersion } = exp;
|
||||
|
||||
// When a SDK version is supplied instead of a runtime version and we're in the managed workflow
|
||||
// construct the runtimeVersion with special meaning indicating that the runtime is an
|
||||
@@ -265,7 +263,7 @@ export default class BranchPublish extends Command {
|
||||
const platforms = platformFlag === 'all' ? defaultPublishPlatforms : [platformFlag];
|
||||
const assets = collectAssets({ inputDir: inputDir!, platforms });
|
||||
await uploadAssetsAsync(assets);
|
||||
updateInfoGroup = await buildUpdateInfoGroupAsync(assets);
|
||||
updateInfoGroup = await buildUpdateInfoGroupAsync(assets, exp);
|
||||
assetSpinner.succeed('Uploaded assets!');
|
||||
} catch (e) {
|
||||
assetSpinner.fail('Failed to upload assets');
|
||||
@@ -307,35 +305,14 @@ export default class BranchPublish extends Command {
|
||||
if (jsonFlag) {
|
||||
Log.log(JSON.stringify(newUpdateGroup));
|
||||
} else {
|
||||
const outputMessage = new Table({
|
||||
wordWrap: true,
|
||||
chars: {
|
||||
top: '',
|
||||
'top-mid': '',
|
||||
'top-left': '',
|
||||
'top-right': '',
|
||||
bottom: '',
|
||||
'bottom-mid': '',
|
||||
'bottom-left': '',
|
||||
'bottom-right': '',
|
||||
left: '',
|
||||
'left-mid': '',
|
||||
mid: '',
|
||||
'mid-mid': '',
|
||||
right: '',
|
||||
'right-mid': '',
|
||||
middle: ' ',
|
||||
},
|
||||
style: { 'padding-left': 0, 'padding-right': 0 },
|
||||
});
|
||||
outputMessage.push(
|
||||
[chalk.dim('project'), `@${accountName}/${slug}`],
|
||||
[chalk.dim('branch'), name],
|
||||
[chalk.dim('runtimeVersion'), runtimeVersion],
|
||||
[chalk.dim('groupID'), newUpdateGroup.group],
|
||||
[chalk.dim('message'), message]
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'branch', value: name },
|
||||
{ label: 'runtime version', value: runtimeVersion },
|
||||
{ label: 'update group ID', value: newUpdateGroup.group },
|
||||
{ label: 'message', value: message },
|
||||
])
|
||||
);
|
||||
Log.log(outputMessage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,13 @@ import {
|
||||
ViewBranchQueryVariables,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { UPDATE_COLUMNS, formatUpdate, getPlatformsForGroup } from '../../update/utils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
|
||||
const PAGE_LIMIT = 10_000;
|
||||
|
||||
type TruncatedUpdate = Pick<Update, 'group' | 'message' | 'createdAt' | 'actor'>;
|
||||
|
||||
export async function viewUpdateBranchAsync({
|
||||
appId,
|
||||
name,
|
||||
@@ -121,7 +116,6 @@ export default class BranchView extends Command {
|
||||
}
|
||||
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!name) {
|
||||
@@ -168,12 +162,16 @@ export default class BranchView extends Command {
|
||||
]);
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
`️Branch: ${chalk.bold(UpdateBranch.name)} on project ${chalk.bold(
|
||||
fullName
|
||||
)}. Branch ID: ${chalk.bold(UpdateBranch.id)}`
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Branch:'));
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: UpdateBranch.name },
|
||||
{ label: 'ID', value: UpdateBranch.id },
|
||||
])
|
||||
);
|
||||
Log.log(chalk.bold('Recent update groups published on this branch:'));
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Recently published update groups:'));
|
||||
Log.log(groupTable.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExpoConfig, getConfig } from '@expo/config';
|
||||
import { EasJsonReader } from '@expo/eas-json';
|
||||
import { flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
@@ -87,7 +88,7 @@ export default class Build extends EasCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
exp = (await ensureProjectConfiguredAsync(projectDir)) ?? exp;
|
||||
exp = (await ensureProjectConfiguredAsync(projectDir, platform)) ?? exp;
|
||||
|
||||
const commandCtx = await createCommandContextAsync({
|
||||
requestedPlatform: platform,
|
||||
@@ -101,6 +102,7 @@ export default class Build extends EasCommand {
|
||||
skipProjectConfiguration: flags['skip-project-configuration'],
|
||||
waitForBuildEnd: flags.wait,
|
||||
});
|
||||
|
||||
await buildAsync(commandCtx);
|
||||
}
|
||||
}
|
||||
@@ -140,17 +142,29 @@ async function promptForPlatformAsync(): Promise<RequestedPlatform> {
|
||||
return platform;
|
||||
}
|
||||
|
||||
async function ensureProjectConfiguredAsync(projectDir: string): Promise<ExpoConfig | null> {
|
||||
if (await fs.pathExists(path.join(projectDir, 'eas.json'))) {
|
||||
async function ensureProjectConfiguredAsync(
|
||||
projectDir: string,
|
||||
platform: RequestedPlatform
|
||||
): Promise<ExpoConfig | null> {
|
||||
const platformsToConfigure = await getPlatformsToConfigureAsync(projectDir, platform);
|
||||
|
||||
if (!platformsToConfigure) {
|
||||
return null;
|
||||
}
|
||||
const confirm = await confirmAsync({
|
||||
message: 'This app is not set up for building with EAS. Set it up now?',
|
||||
});
|
||||
|
||||
// Ensure the prompt is consistent with the platforms we need to configure
|
||||
let message = 'This project is not configured to build with EAS. Set it up now?';
|
||||
if (platformsToConfigure === RequestedPlatform.Ios) {
|
||||
message = 'Your iOS project is not configured to build with EAS. Set it up now?';
|
||||
} else if (platformsToConfigure === RequestedPlatform.Android) {
|
||||
message = 'Your Android project is not configured to build with EAS. Set it up now?';
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({ message });
|
||||
if (confirm) {
|
||||
await configureAsync({
|
||||
projectDir,
|
||||
platform: RequestedPlatform.All,
|
||||
platform: platformsToConfigure,
|
||||
});
|
||||
if (await vcs.hasUncommittedChangesAsync()) {
|
||||
throw new Error(
|
||||
@@ -167,3 +181,30 @@ async function ensureProjectConfiguredAsync(projectDir: string): Promise<ExpoCon
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getPlatformsToConfigureAsync(
|
||||
projectDir: string,
|
||||
platform: RequestedPlatform
|
||||
): Promise<RequestedPlatform | null> {
|
||||
if (!(await fs.pathExists(path.join(projectDir, 'eas.json')))) {
|
||||
return platform;
|
||||
}
|
||||
|
||||
const easConfig = await new EasJsonReader(projectDir, platform).readRawAsync();
|
||||
if (platform === RequestedPlatform.All) {
|
||||
if (easConfig.builds?.android && easConfig.builds?.ios) {
|
||||
return null;
|
||||
} else if (easConfig.builds?.ios) {
|
||||
return RequestedPlatform.Android;
|
||||
} else if (easConfig.builds?.android) {
|
||||
return RequestedPlatform.Ios;
|
||||
}
|
||||
} else if (
|
||||
(platform === RequestedPlatform.Android || platform === RequestedPlatform.Ios) &&
|
||||
easConfig.builds?.[platform]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return platform;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { createUpdateBranchOnAppAsync } from '../branch/create';
|
||||
|
||||
async function createUpdateChannelOnAppAsync({
|
||||
@@ -87,7 +88,6 @@ export default class ChannelCreate extends Command {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!channelName) {
|
||||
@@ -140,10 +140,26 @@ export default class ChannelCreate extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(
|
||||
`️Created a new channel ${chalk.bold(newChannel.name)} on project ${chalk.bold(
|
||||
fullName
|
||||
)}. ${branchMessage} and have pointed the channel at it. You can now update your app by publishing!`
|
||||
`Created a new channel on project ${chalk.bold(await getProjectFullNameAsync(exp))}`
|
||||
);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: newChannel.id },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.withTick(`${branchMessage} and have pointed the channel at it.`);
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: newChannel.name },
|
||||
{ label: 'ID', value: branchId },
|
||||
])
|
||||
);
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('You can now update your app by publishing!'));
|
||||
}
|
||||
}
|
||||
@@ -146,13 +146,6 @@ export default class ChannelEdit extends Command {
|
||||
'There is a rollout in progress. Please manage it with "channel:rollout" instead.'
|
||||
);
|
||||
}
|
||||
const existingBranch = existingChannel.updateBranches[0];
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
chalk`Channel {bold ${existingChannel.name}} is currently set to branch {bold ${existingBranch.name}}`
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
if (!branchName) {
|
||||
const validationMessage = 'branch name may not be empty.';
|
||||
@@ -185,6 +178,7 @@ export default class ChannelEdit extends Command {
|
||||
Log.withTick(
|
||||
chalk`Channel {bold ${channel.name}} is now set to branch {bold ${branch.name}}.\n`
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
chalk`Users with builds on channel {bold ${channel.name}} will now receive the active update on {bold ${branch.name}}.`
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
import { logChannelDetails } from './view';
|
||||
|
||||
const CHANNEL_LIMIT = 10_000;
|
||||
@@ -99,9 +99,14 @@ export default class ChannelList extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.log(); // spacing
|
||||
for (const channel of channels) {
|
||||
Log.log(`Details of channel ${chalk.bold(chalk.cyan(channel.name))}:`);
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: channel.name },
|
||||
{ label: 'ID', value: channel.id },
|
||||
])
|
||||
);
|
||||
logChannelDetails(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,7 @@ import {
|
||||
GetChannelByNameForAppQueryVariables,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import {
|
||||
FormatUpdateParameter,
|
||||
@@ -23,6 +19,7 @@ import {
|
||||
formatUpdate,
|
||||
getPlatformsForGroup,
|
||||
} from '../../update/utils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
|
||||
export type BranchMapping = {
|
||||
version: number;
|
||||
@@ -220,7 +217,6 @@ export default class ChannelView extends Command {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const fullName = await getProjectFullNameAsync(exp);
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
|
||||
if (!channelName) {
|
||||
@@ -250,9 +246,15 @@ export default class ChannelView extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.withTick(
|
||||
chalk`Channel: {bold ${channel.name}} on project {bold ${fullName}}. Channel ID: {bold ${channel.id}}`
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Channel:'));
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'Name', value: channel.name },
|
||||
{ label: 'ID', value: channel.id },
|
||||
])
|
||||
);
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
chalk`{bold Branches, pointed at by this channel, and their most recent update group:}`
|
||||
);
|
||||
|
||||
@@ -45,7 +45,6 @@ If you are not sure what is the UDID of the device you are looking for, run:
|
||||
spinner.fail(`Couldn't find a device with the UDID ${UDID}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
spinner.fail(`Something went wrong and we couldn't fetch the device with UDID ${UDID}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getConfig } from '@expo/config';
|
||||
import { Command } from '@oclif/command';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../../graphql/client';
|
||||
import { AppInfoQuery, AppInfoQueryVariables } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { findProjectRootAsync, getProjectIdAsync } from '../../project/projectUtils';
|
||||
import formatFields from '../../utils/formatFields';
|
||||
|
||||
async function projectInfoByIdAsync(appId: string): Promise<AppInfoQuery> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<AppInfoQuery, AppInfoQueryVariables>(
|
||||
gql`
|
||||
query AppInfo($appId: String!) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default class ProjectInfo extends Command {
|
||||
static description = 'information about the current project';
|
||||
|
||||
async run() {
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
if (!projectDir) {
|
||||
throw new Error('Please run this command inside a project directory.');
|
||||
}
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const { app } = await projectInfoByIdAsync(projectId);
|
||||
if (!app) {
|
||||
throw new Error(`Could not find project with ID: ${projectId}`);
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
formatFields([
|
||||
{ label: 'fullName', value: app.byId.fullName },
|
||||
{ label: 'ID', value: projectId },
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -2,12 +2,12 @@ import { getConfig } from '@expo/config';
|
||||
import { Command } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import Log from '../log';
|
||||
import { findProjectRootAsync, setProjectIdAsync } from '../project/projectUtils';
|
||||
import Log from '../../log';
|
||||
import { findProjectRootAsync, setProjectIdAsync } from '../../project/projectUtils';
|
||||
|
||||
export default class InitView extends Command {
|
||||
static hidden = true;
|
||||
static description = 'Create or link an EAS project';
|
||||
export default class ProjectInit extends Command {
|
||||
static description = 'create or link an EAS project';
|
||||
static aliases = ['init'];
|
||||
|
||||
async run() {
|
||||
const projectDir = await findProjectRootAsync(process.cwd());
|
||||
@@ -3,6 +3,10 @@ import { Command, flags } from '@oclif/command';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
||||
import {
|
||||
EnvironmentSecretScope,
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
import Log from '../../log';
|
||||
import {
|
||||
isEasEnabledForProjectAsync,
|
||||
@@ -18,10 +22,6 @@ import { findAccountByName } from '../../user/Account';
|
||||
import { getActorDisplayName } from '../../user/User';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
|
||||
export enum EnvironmentSecretScope {
|
||||
ACCOUNT = 'account',
|
||||
PROJECT = 'project',
|
||||
}
|
||||
export default class EnvironmentSecretCreate extends Command {
|
||||
static description = 'Create an environment secret on the current project or owner account.';
|
||||
|
||||
@@ -37,12 +37,16 @@ export default class EnvironmentSecretCreate extends Command {
|
||||
value: flags.string({
|
||||
description: 'Value of the secret',
|
||||
}),
|
||||
force: flags.boolean({
|
||||
description: 'Delete and recreate existing secrets',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
const actor = await ensureLoggedInAsync();
|
||||
let {
|
||||
flags: { name, value: secretValue, scope },
|
||||
flags: { name, value: secretValue, scope, force },
|
||||
} = this.parse(EnvironmentSecretCreate);
|
||||
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
@@ -93,7 +97,9 @@ export default class EnvironmentSecretCreate extends Command {
|
||||
},
|
||||
}));
|
||||
|
||||
if (!name) throw new Error('Secret name may not be empty.');
|
||||
if (!name) {
|
||||
throw new Error('Secret name may not be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!secretValue) {
|
||||
@@ -106,10 +112,26 @@ export default class EnvironmentSecretCreate extends Command {
|
||||
validate: value => (value ? true : validationMessage),
|
||||
}));
|
||||
|
||||
if (!secretValue) throw new Error(validationMessage);
|
||||
if (!secretValue) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (scope === EnvironmentSecretScope.PROJECT) {
|
||||
if (force) {
|
||||
const existingSecrets = await EnvironmentSecretsQuery.byAppIdAsync(projectId);
|
||||
const existingSecret = existingSecrets.find(secret => secret.name === name);
|
||||
|
||||
if (existingSecret) {
|
||||
await EnvironmentSecretMutation.delete(existingSecret.id);
|
||||
Log.withTick(
|
||||
`Deleting existing secret ${chalk.bold(name)} on project ${chalk.bold(
|
||||
`@${accountName}/${slug}`
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const secret = await EnvironmentSecretMutation.createForApp(
|
||||
{ name, value: secretValue },
|
||||
projectId
|
||||
@@ -136,6 +158,20 @@ export default class EnvironmentSecretCreate extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
const existingSecrets = await EnvironmentSecretsQuery.byAcccountNameAsync(projectId);
|
||||
const existingSecret = existingSecrets.find(secret => secret.name === name);
|
||||
|
||||
if (existingSecret) {
|
||||
await EnvironmentSecretMutation.delete(existingSecret.id);
|
||||
Log.withTick(
|
||||
`Deleting existing secret ${chalk.bold(name)} on account ${chalk.bold(
|
||||
ownerAccount.name
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const secret = await EnvironmentSecretMutation.createForAccount(
|
||||
{ name, value: secretValue },
|
||||
ownerAccount.id
|
||||
|
||||
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
||||
|
||||
import { EnvironmentSecretMutation } from '../../graphql/mutations/EnvironmentSecretMutation';
|
||||
import {
|
||||
EnvironmentSecretScope,
|
||||
EnvironmentSecretWithScope,
|
||||
EnvironmentSecretsQuery,
|
||||
} from '../../graphql/queries/EnvironmentSecretsQuery';
|
||||
@@ -15,12 +16,10 @@ import {
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync, toggleConfirmAsync } from '../../prompts';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { EnvironmentSecretScope } from './create';
|
||||
|
||||
export default class EnvironmentSecretDelete extends Command {
|
||||
static description = `Delete an environment secret by ID.
|
||||
@@ -38,7 +37,6 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
@@ -55,7 +53,7 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
if (!id) {
|
||||
const validationMessage = 'You must select which secret to delete.';
|
||||
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName);
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectId);
|
||||
|
||||
({ secret } = await promptAsync({
|
||||
type: 'autocomplete',
|
||||
@@ -69,7 +67,9 @@ Unsure where to find the secret's ID? Run ${chalk.bold('eas secrets:list')}`;
|
||||
|
||||
id = secret?.id;
|
||||
|
||||
if (!id) throw new Error(validationMessage);
|
||||
if (!id) {
|
||||
throw new Error(validationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountNameAsync,
|
||||
getProjectFullNameAsync,
|
||||
getProjectIdAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
@@ -27,7 +26,6 @@ export default class EnvironmentSecretList extends Command {
|
||||
const projectDir = (await findProjectRootAsync()) ?? process.cwd();
|
||||
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
|
||||
const projectId = await getProjectIdAsync(exp);
|
||||
const projectFullName = await getProjectFullNameAsync(exp);
|
||||
const projectAccountName = await getProjectAccountNameAsync(exp);
|
||||
|
||||
if (!(await isEasEnabledForProjectAsync(projectId))) {
|
||||
@@ -40,7 +38,7 @@ export default class EnvironmentSecretList extends Command {
|
||||
throw new Error("Please run this command inside your project's directory");
|
||||
}
|
||||
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectFullName);
|
||||
const secrets = await EnvironmentSecretsQuery.allAsync(projectAccountName, projectId);
|
||||
|
||||
const table = new Table({
|
||||
head: ['Name', 'Scope', 'ID', 'Updated at'],
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class AndroidCredentialsProvider {
|
||||
keystore: nullthrows(androidBuildCredentials.androidKeystore?.keystore),
|
||||
keystorePassword: nullthrows(androidBuildCredentials.androidKeystore?.keystorePassword),
|
||||
keyAlias: nullthrows(androidBuildCredentials.androidKeystore?.keyAlias),
|
||||
keyPassword: nullthrows(androidBuildCredentials.androidKeystore?.keyPassword),
|
||||
keyPassword: androidBuildCredentials.androidKeystore?.keyPassword ?? undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { nanoid } from 'nanoid';
|
||||
import ora from 'ora';
|
||||
|
||||
import { AndroidAppBuildCredentialsFragment } from '../../../graphql/generated';
|
||||
import { getApplicationId } from '../../../project/android/applicationId';
|
||||
import { getApplicationIdAsync } from '../../../project/android/applicationId';
|
||||
import { getProjectAccountName, getProjectConfigDescription } from '../../../project/projectUtils';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { findAccountByName } from '../../../user/Account';
|
||||
@@ -86,7 +86,7 @@ export async function promptUserAndCopyLegacyCredentialsAsync(
|
||||
spinner.succeed('Credentials copied to EAS.');
|
||||
}
|
||||
|
||||
export function getAppLookupParamsFromContext(ctx: Context): AppLookupParams {
|
||||
export async function getAppLookupParamsFromContextAsync(ctx: Context): Promise<AppLookupParams> {
|
||||
ctx.ensureProjectContext();
|
||||
const projectName = ctx.exp.slug;
|
||||
const accountName = getProjectAccountName(ctx.exp, ctx.user);
|
||||
@@ -95,7 +95,7 @@ export function getAppLookupParamsFromContext(ctx: Context): AppLookupParams {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
|
||||
const androidApplicationIdentifier = getApplicationId(ctx.projectDir, ctx.exp);
|
||||
const androidApplicationIdentifier = await getApplicationIdAsync(ctx.projectDir, ctx.exp);
|
||||
if (!androidApplicationIdentifier) {
|
||||
throw new Error(
|
||||
`android.package needs to be defined in your ${getProjectConfigDescription(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { testLegacyAndroidFcmFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { AssignFcm } from '../AssignFcm';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
|
||||
describe(AssignFcm, () => {
|
||||
it('assigns an fcm api key in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const assignFcmAction = new AssignFcm(appLookupParams);
|
||||
await assignFcmAction.runAsync(ctx, testLegacyAndroidFcmFragment);
|
||||
|
||||
@@ -22,7 +22,7 @@ describe(AssignFcm, () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const assignFcmAction = new AssignFcm(appLookupParams);
|
||||
|
||||
// dont fail if users are running in non-interactive mode
|
||||
|
||||
+7
-18
@@ -10,24 +10,13 @@ import {
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import {
|
||||
canCopyLegacyCredentialsAsync,
|
||||
getAppLookupParamsFromContext,
|
||||
getAppLookupParamsFromContextAsync,
|
||||
promptUserAndCopyLegacyCredentialsAsync,
|
||||
} from '../BuildCredentialsUtils';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
describe('BuildCredentialsUtils', () => {
|
||||
describe(canCopyLegacyCredentialsAsync, () => {
|
||||
it('returns true if the user has legacy credentials and no modern ones', async () => {
|
||||
@@ -41,7 +30,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
expect(canCopyLegacyCredentials).toBe(true);
|
||||
});
|
||||
@@ -58,7 +47,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
expect(canCopyLegacyCredentials).toBe(false);
|
||||
});
|
||||
@@ -71,7 +60,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
getLegacyAndroidAppCredentialsWithCommonFieldsAsync: jest.fn(() => null),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
expect(canCopyLegacyCredentials).toBe(false);
|
||||
});
|
||||
@@ -86,7 +75,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
expect(canCopyLegacyCredentials).toBe(true);
|
||||
});
|
||||
@@ -109,7 +98,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
createFcmAsync: jest.fn(() => testLegacyAndroidFcmFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
await promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams);
|
||||
|
||||
expect(
|
||||
@@ -131,7 +120,7 @@ describe('BuildCredentialsUtils', () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
await expect(
|
||||
promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams)
|
||||
).rejects.toThrowError();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { promptAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { CreateFcm } from '../CreateFcm';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
@@ -9,7 +9,7 @@ jest.mock('../../../../prompts');
|
||||
describe(CreateFcm, () => {
|
||||
it('creates an fcm api key in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createFcmAction = new CreateFcm(appLookupParams.account);
|
||||
await createFcmAction.runAsync(ctx);
|
||||
|
||||
@@ -18,7 +18,7 @@ describe(CreateFcm, () => {
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createFcmAction = new CreateFcm(appLookupParams.account);
|
||||
|
||||
// fail if users are running in non-interactive mode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { CreateKeystore } from '../CreateKeystore';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
@@ -9,7 +9,7 @@ jest.mock('../../../../prompts');
|
||||
describe('CreateKeystore', () => {
|
||||
it('creates a keystore in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createKeystoreAction = new CreateKeystore(appLookupParams.account);
|
||||
await createKeystoreAction.runAsync(ctx);
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('CreateKeystore', () => {
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const createKeystoreAction = new CreateKeystore(appLookupParams.account);
|
||||
|
||||
// fail if users are running in non-interactive mode
|
||||
|
||||
+5
-13
@@ -2,26 +2,18 @@ import fs from 'fs-extra';
|
||||
|
||||
import { testAndroidBuildCredentialsFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { DownloadKeystore } from '../DownloadKeystore';
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
jest.mock('fs-extra');
|
||||
|
||||
describe(DownloadKeystore, () => {
|
||||
it('downloads a keystore', async () => {
|
||||
const fsWriteFileSpy = jest.spyOn(fs, 'writeFile');
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const downloadKeystoreAction = new DownloadKeystore({ app: appLookupParams });
|
||||
await downloadKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment);
|
||||
expect(fsWriteFileSpy as any).toHaveBeenCalledTimes(1);
|
||||
@@ -34,7 +26,7 @@ describe(DownloadKeystore, () => {
|
||||
});
|
||||
it('works in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const downloadKeystoreAction = new DownloadKeystore({ app: appLookupParams });
|
||||
await expect(
|
||||
downloadKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment)
|
||||
|
||||
@@ -4,22 +4,12 @@ import {
|
||||
testAndroidAppCredentialsFragment,
|
||||
} from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { RemoveFcm } from '../RemoveFcm';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
describe(RemoveFcm, () => {
|
||||
it('removes an FCM Api Key', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -31,14 +21,14 @@ describe(RemoveFcm, () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeFcmApiKeyAction = new RemoveFcm(appLookupParams);
|
||||
await removeFcmApiKeyAction.runAsync(ctx);
|
||||
expect(ctx.android.deleteFcmAsync as any).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeFcmApiKeyAction = new RemoveFcm(appLookupParams);
|
||||
await expect(removeFcmApiKeyAction.runAsync(ctx)).rejects.toThrowError();
|
||||
});
|
||||
|
||||
@@ -1,35 +1,26 @@
|
||||
import { confirmAsync } from '../../../../prompts';
|
||||
import { testAndroidBuildCredentialsFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { RemoveKeystore } from '../RemoveKeystore';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
describe(RemoveKeystore, () => {
|
||||
it('removes a keystore', async () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: false,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeKeystoreAction = new RemoveKeystore(appLookupParams);
|
||||
await removeKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment);
|
||||
expect(ctx.android.deleteKeystoreAsync as any).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('errors in Non-Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: true });
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const removeKeystoreAction = new RemoveKeystore(appLookupParams);
|
||||
await expect(
|
||||
removeKeystoreAction.runAsync(ctx, testAndroidBuildCredentialsFragment)
|
||||
|
||||
+4
-15
@@ -6,23 +6,12 @@ import {
|
||||
} from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupBuildCredentials } from '../SetupBuildCredentials';
|
||||
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
describe('SetupBuildCredentials', () => {
|
||||
it('skips setup when there are prior credentials', async () => {
|
||||
const ctx = createCtxMock({
|
||||
@@ -34,7 +23,7 @@ describe('SetupBuildCredentials', () => {
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
@@ -50,7 +39,7 @@ describe('SetupBuildCredentials', () => {
|
||||
createKeystoreAsync: jest.fn(() => testJksAndroidKeystoreFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await setupBuildCredentialsAction.runAsync(ctx);
|
||||
|
||||
@@ -65,7 +54,7 @@ describe('SetupBuildCredentials', () => {
|
||||
...getNewAndroidApiMock(),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
|
||||
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
|
||||
+4
-14
@@ -12,7 +12,7 @@ import {
|
||||
SelectAndroidBuildCredentials,
|
||||
SelectAndroidBuildCredentialsResultType,
|
||||
} from '../../../manager/SelectAndroidBuildCredentials';
|
||||
import { getAppLookupParamsFromContext } from '../BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import { SetupBuildCredentialsFromCredentialsJson } from '../SetupBuildCredentialsFromCredentialsJson';
|
||||
|
||||
jest.mock('fs');
|
||||
@@ -21,16 +21,6 @@ jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
jest.mock('../../../manager/SelectAndroidBuildCredentials');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
@@ -67,7 +57,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
}),
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
@@ -117,7 +107,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
}),
|
||||
'keystore.jks': 'some-binary-content',
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
@@ -140,7 +130,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
|
||||
appLookupParams
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import mockdate from 'mockdate';
|
||||
import Log from '../../../../log';
|
||||
import { testAndroidAppCredentialsFragment } from '../../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../../actions/BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../../actions/BuildCredentialsUtils';
|
||||
import { displayAndroidAppCredentials } from '../printCredentials';
|
||||
|
||||
jest.mock('../../../../log');
|
||||
@@ -14,7 +14,7 @@ mockdate.set(new Date('4/20/2021'));
|
||||
describe('print credentials', () => {
|
||||
it('prints the AndroidAppCredentials fragment', async () => {
|
||||
const ctx = createCtxMock();
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
displayAndroidAppCredentials({
|
||||
appLookupParams,
|
||||
appCredentials: testAndroidAppCredentialsFragment,
|
||||
|
||||
@@ -22,22 +22,6 @@ import { updateAndroidCredentialsAsync, updateIosCredentialsAsync } from '../upd
|
||||
jest.mock('fs');
|
||||
jest.mock('prompts');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
console.error = jest.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
console.error = originalConsoleError;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
asMock(prompts).mockReset();
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { CredentialsSource, IosDistributionType, IosEnterpriseProvisioning } from '@expo/eas-json';
|
||||
|
||||
import { CommonIosAppCredentialsFragment } from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import { findApplicationTarget } from '../../project/ios/target';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { Context } from '../context';
|
||||
import * as credentialsJsonReader from '../credentialsJson/read';
|
||||
import { ensureAllTargetsAreConfigured } from '../credentialsJson/utils';
|
||||
import { getAppFromContext } from './actions/BuildCredentialsUtils';
|
||||
import { SetupBuildCredentials } from './actions/SetupBuildCredentials';
|
||||
import { SetupPushKey } from './actions/SetupPushKey';
|
||||
import { IosCapabilitiesOptions } from './appstore/ensureAppExists';
|
||||
import { App, IosCredentials, Target } from './types';
|
||||
import { isAdHocProfile } from './utils/provisioningProfile';
|
||||
@@ -26,11 +31,14 @@ export default class IosCredentialsProvider {
|
||||
public async getCredentialsAsync(
|
||||
src: CredentialsSource.LOCAL | CredentialsSource.REMOTE
|
||||
): Promise<IosCredentials> {
|
||||
let buildCredentials;
|
||||
if (src === CredentialsSource.LOCAL) {
|
||||
return await this.getLocalAsync();
|
||||
buildCredentials = await this.getLocalAsync();
|
||||
} else {
|
||||
return await this.getRemoteAsync();
|
||||
buildCredentials = await this.getRemoteAsync();
|
||||
}
|
||||
await this.getPushKeyAsync(this.ctx, this.options.targets);
|
||||
return buildCredentials;
|
||||
}
|
||||
|
||||
private async getLocalAsync(): Promise<IosCredentials> {
|
||||
@@ -59,6 +67,40 @@ export default class IosCredentialsProvider {
|
||||
}).runAsync(this.ctx);
|
||||
}
|
||||
|
||||
private async getPushKeyAsync(
|
||||
ctx: Context,
|
||||
targets: Target[]
|
||||
): Promise<CommonIosAppCredentialsFragment | null> {
|
||||
if (ctx.nonInteractive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const applicationTarget = findApplicationTarget(targets);
|
||||
const app = getAppFromContext(ctx);
|
||||
const appLookupParams = {
|
||||
...app,
|
||||
bundleIdentifier: applicationTarget.bundleIdentifier,
|
||||
parentBundleIdentifier: applicationTarget.parentBundleIdentifier,
|
||||
};
|
||||
|
||||
const setupPushKeyAction = new SetupPushKey(appLookupParams);
|
||||
const isPushKeySetup = await setupPushKeyAction.isPushKeySetupAsync(ctx);
|
||||
if (isPushKeySetup) {
|
||||
Log.succeed(
|
||||
`Push Notifications setup for ${app.projectName}:${applicationTarget.bundleIdentifier}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const confirmSetup = await confirmAsync({
|
||||
message: `Would you like to setup Push Notifications for your project?`,
|
||||
});
|
||||
if (!confirmSetup) {
|
||||
return null;
|
||||
}
|
||||
return await setupPushKeyAction.runAsync(ctx);
|
||||
}
|
||||
|
||||
private assertProvisioningProfileType(provisioningProfile: string, targetName?: string): void {
|
||||
const isAdHoc = isAdHocProfile(provisioningProfile);
|
||||
if (this.options.distribution === 'internal' && !isAdHoc) {
|
||||
|
||||
@@ -29,20 +29,6 @@ jest.mock('../validators/validateProvisioningProfile', () => ({
|
||||
}));
|
||||
jest.mock('../../../project/ios/bundleIdentifier');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleError = console.error;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.error = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.error = originalConsoleError;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
-8
@@ -12,14 +12,6 @@ import { RemoveDistributionCertificate } from '../RemoveDistributionCertificate'
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
});
|
||||
|
||||
describe('RemoveDistributionCertificate', () => {
|
||||
it('deletes the distribution certificate on Expo and Apple servers when there are no App Dependencies in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
|
||||
@@ -8,14 +8,6 @@ import { RemovePushKey } from '../RemovePushKey';
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
});
|
||||
|
||||
describe(RemovePushKey, () => {
|
||||
it('deletes the push key on Expo and Apple servers in Interactive Mode', async () => {
|
||||
const ctx = createCtxMock({ nonInteractive: false });
|
||||
|
||||
-8
@@ -13,14 +13,6 @@ jest.mock('../SetupAdhocProvisioningProfile');
|
||||
jest.mock('../SetupProvisioningProfile');
|
||||
jest.mock('../BuildCredentialsUtils', () => ({ getAllBuildCredentialsAsync: jest.fn() }));
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(promptAsync).mockReset();
|
||||
|
||||
|
||||
@@ -9,14 +9,6 @@ import { SetupPushKey } from '../SetupPushKey';
|
||||
jest.mock('../../../../prompts');
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
});
|
||||
|
||||
describe(SetupPushKey, () => {
|
||||
it('skips setting up a Push Key if it is already configured', async () => {
|
||||
const ctx = createCtxMock({
|
||||
|
||||
-11
@@ -20,17 +20,6 @@ import { SetupTargetBuildCredentialsFromCredentialsJson } from '../SetupTargetBu
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../credentialsJson/read');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(confirmAsync).mockReset();
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
|
||||
+40
-1
@@ -1,6 +1,21 @@
|
||||
import { BundleIdCapability, CapabilityType } from '@expo/apple-utils';
|
||||
|
||||
import { syncCapabilitiesForEntitlementsAsync } from '../bundleIdCapabilities';
|
||||
import {
|
||||
CapabilityMapping,
|
||||
assertValidOptions,
|
||||
syncCapabilitiesForEntitlementsAsync,
|
||||
} from '../bundleIdCapabilities';
|
||||
|
||||
describe(assertValidOptions, () => {
|
||||
it(`adds a reason for asserting capability identifiers`, () => {
|
||||
const classifier = CapabilityMapping.find(
|
||||
({ capabilityIdPrefix }) => capabilityIdPrefix === 'merchant.'
|
||||
)!;
|
||||
expect(() => assertValidOptions(classifier, ['foobar'])).toThrowError(
|
||||
/Expected an array of strings, where each string is prefixed with "merchant."/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
it('enables all capabilities', async () => {
|
||||
@@ -33,6 +48,10 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
'com.apple.developer.applesignin': ['Default'],
|
||||
'com.apple.developer.siri': true,
|
||||
'com.apple.developer.networking.wifi-info': true,
|
||||
'com.apple.developer.usernotifications.communication': true,
|
||||
'com.apple.developer.usernotifications.time-sensitive': true,
|
||||
'com.apple.developer.group-session': true,
|
||||
'com.apple.developer.family-controls': true,
|
||||
'com.apple.developer.authentication-services.autofill-credential-provider': true,
|
||||
// 'com.apple.developer.game-center': true,
|
||||
'com.apple.security.application-groups': [
|
||||
@@ -61,6 +80,22 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
{ capabilityType: 'APPLE_ID_AUTH', option: 'ON' },
|
||||
{ capabilityType: 'SIRIKIT', option: 'ON' },
|
||||
{ capabilityType: 'ACCESS_WIFI_INFORMATION', option: 'ON' },
|
||||
{
|
||||
capabilityType: 'USERNOTIFICATIONS_COMMUNICATION',
|
||||
option: 'ON',
|
||||
},
|
||||
{
|
||||
capabilityType: 'USERNOTIFICATIONS_TIMESENSITIVE',
|
||||
option: 'ON',
|
||||
},
|
||||
{
|
||||
capabilityType: 'GROUP_ACTIVITIES',
|
||||
option: 'ON',
|
||||
},
|
||||
{
|
||||
capabilityType: 'FAMILY_CONTROLS',
|
||||
option: 'ON',
|
||||
},
|
||||
{ capabilityType: 'AUTOFILL_CREDENTIAL_PROVIDER', option: 'ON' },
|
||||
{ capabilityType: 'APP_GROUPS', option: 'ON' },
|
||||
]);
|
||||
@@ -86,6 +121,10 @@ describe(syncCapabilitiesForEntitlementsAsync, () => {
|
||||
'Sign In with Apple',
|
||||
'SiriKit',
|
||||
'Access WiFi Information',
|
||||
'Communication Notifications',
|
||||
'Time Sensitive Notifications',
|
||||
'Group Activities',
|
||||
'Family Controls',
|
||||
'AutoFill Credential Provider',
|
||||
'App Groups',
|
||||
]);
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import { syncCapabilityIdentifiersForEntitlementsAsync } from '../capabilityIdentifiers';
|
||||
|
||||
function mockCapabilities(Apple: any) {
|
||||
Apple.MerchantId.getAsync = jest.fn(() => [
|
||||
{
|
||||
id: 'XXX-merch-1',
|
||||
attributes: {
|
||||
identifier: 'merchant.expo',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
Apple.MerchantId.createAsync = jest.fn((ctx, { identifier }) => ({
|
||||
id: 'XXX-merch-2',
|
||||
attributes: {
|
||||
identifier,
|
||||
},
|
||||
}));
|
||||
|
||||
Apple.AppGroup.getAsync = jest.fn(() => [
|
||||
{
|
||||
id: 'XXX-group-1',
|
||||
attributes: {
|
||||
identifier: 'group.expo',
|
||||
},
|
||||
},
|
||||
]);
|
||||
Apple.AppGroup.createAsync = jest.fn((ctx, { identifier }) => ({
|
||||
id: 'XXX-group-2',
|
||||
attributes: {
|
||||
identifier,
|
||||
},
|
||||
}));
|
||||
|
||||
Apple.CloudContainer.getAsync = jest.fn(() => [
|
||||
{
|
||||
id: 'XXX-icloud-1',
|
||||
attributes: {
|
||||
identifier: 'iCloud.expo',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
Apple.CloudContainer.createAsync = jest.fn((ctx, { identifier }) => ({
|
||||
id: 'XXX-icloud-2',
|
||||
attributes: {
|
||||
identifier,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
describe(syncCapabilityIdentifiersForEntitlementsAsync, () => {
|
||||
it(`creates missing capability identifiers`, async () => {
|
||||
const Apple = require('@expo/apple-utils');
|
||||
mockCapabilities(Apple);
|
||||
|
||||
const bundleId = {
|
||||
context: {},
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
await syncCapabilityIdentifiersForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.in-app-payments': ['merchant.bacon'],
|
||||
'com.apple.security.application-groups': ['group.bacon'],
|
||||
'com.apple.developer.icloud-container-identifiers': ['iCloud.bacon'],
|
||||
});
|
||||
|
||||
// Ensure we create missing ids
|
||||
expect(Apple.MerchantId.createAsync).toHaveBeenLastCalledWith(
|
||||
// auth context
|
||||
{},
|
||||
// props
|
||||
{ identifier: 'merchant.bacon' }
|
||||
);
|
||||
// Ensure we create missing ids
|
||||
expect(Apple.AppGroup.createAsync).toHaveBeenLastCalledWith(
|
||||
// auth context
|
||||
{},
|
||||
// props
|
||||
{ identifier: 'group.bacon' }
|
||||
);
|
||||
// Ensure we create missing ids
|
||||
expect(Apple.CloudContainer.createAsync).toHaveBeenLastCalledWith(
|
||||
// auth context
|
||||
{},
|
||||
// props
|
||||
{ identifier: 'iCloud.bacon' }
|
||||
);
|
||||
|
||||
expect(bundleId.updateBundleIdCapabilityAsync).toBeCalledWith([
|
||||
{
|
||||
capabilityType: 'APP_GROUPS',
|
||||
option: 'ON',
|
||||
relationships: {
|
||||
appGroups: ['XXX-group-2'],
|
||||
},
|
||||
},
|
||||
{
|
||||
capabilityType: 'APPLE_PAY',
|
||||
option: 'ON',
|
||||
relationships: {
|
||||
merchantIds: ['XXX-merch-2'],
|
||||
},
|
||||
},
|
||||
{
|
||||
capabilityType: 'ICLOUD',
|
||||
option: 'ON',
|
||||
relationships: {
|
||||
cloudContainers: ['XXX-icloud-2'],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it(`removes local duplicates`, async () => {
|
||||
const Apple = require('@expo/apple-utils');
|
||||
mockCapabilities(Apple);
|
||||
const bundleId = {
|
||||
context: {},
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
await syncCapabilityIdentifiersForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.in-app-payments': ['merchant.expo', 'merchant.expo'],
|
||||
});
|
||||
|
||||
// Only called once because we remove local duplicates to minimize network requests.
|
||||
expect(Apple.MerchantId.getAsync).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`creates missing capability identifiers`, async () => {
|
||||
const Apple = require('@expo/apple-utils');
|
||||
mockCapabilities(Apple);
|
||||
const bundleId = {
|
||||
context: {},
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
await syncCapabilityIdentifiersForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.in-app-payments': ['merchant.expo'],
|
||||
});
|
||||
|
||||
expect(Apple.MerchantId.createAsync).not.toBeCalled();
|
||||
});
|
||||
|
||||
it(`throws when creating a missing capability that is reserved`, async () => {
|
||||
const Apple = require('@expo/apple-utils');
|
||||
mockCapabilities(Apple);
|
||||
Apple.MerchantId.createAsync = jest.fn((ctx: any, { identifier }: { identifier: string }) => {
|
||||
// e2e test with: merchant.expodemo
|
||||
throw new Error(
|
||||
`There is a problem with the request entity - A Merchant ID with Identifier '${identifier}' is not available. Please enter a different string.`
|
||||
);
|
||||
});
|
||||
const bundleId = {
|
||||
context: {},
|
||||
updateBundleIdCapabilityAsync: jest.fn(),
|
||||
id: 'XXX',
|
||||
} as any;
|
||||
|
||||
await expect(
|
||||
syncCapabilityIdentifiersForEntitlementsAsync(bundleId, {
|
||||
'com.apple.developer.in-app-payments': ['merchant.bacon'],
|
||||
})
|
||||
).rejects.toThrow(/is not available. Please enter a different string/);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,20 @@
|
||||
import {
|
||||
AppGroup,
|
||||
BundleId,
|
||||
BundleIdCapability,
|
||||
CapabilityOptionMap,
|
||||
CapabilityType,
|
||||
CapabilityTypeDataProtectionOption,
|
||||
CapabilityTypeOption,
|
||||
CloudContainer,
|
||||
MerchantId,
|
||||
} from '@expo/apple-utils';
|
||||
import { JSONObject, JSONValue } from '@expo/json-file';
|
||||
import getenv from 'getenv';
|
||||
|
||||
import Log from '../../../log';
|
||||
|
||||
const EXPO_NO_CAPABILITY_SYNC = getenv.boolish('EXPO_NO_CAPABILITY_SYNC', false);
|
||||
export const EXPO_NO_CAPABILITY_SYNC = getenv.boolish('EXPO_NO_CAPABILITY_SYNC', false);
|
||||
|
||||
type GetOptionsMethod<T extends CapabilityType = any> = (
|
||||
entitlement: JSONValue,
|
||||
@@ -22,6 +25,13 @@ const validateBooleanOptions = (options: any): boolean => {
|
||||
return typeof options === 'boolean';
|
||||
};
|
||||
|
||||
const validatePrefixedStringArrayOptions = (prefix: string) => (options: any): boolean => {
|
||||
return (
|
||||
Array.isArray(options) &&
|
||||
options.every(option => typeof option === 'string' && option.startsWith(prefix))
|
||||
);
|
||||
};
|
||||
|
||||
const validateStringArrayOptions = (options: any): boolean => {
|
||||
return Array.isArray(options) && options.every(option => typeof option === 'string');
|
||||
};
|
||||
@@ -63,7 +73,9 @@ export async function syncCapabilitiesForEntitlementsAsync(
|
||||
bundleId: BundleId,
|
||||
entitlements: JSONObject = {}
|
||||
): Promise<{ enabled: string[]; disabled: string[] }> {
|
||||
if (EXPO_NO_CAPABILITY_SYNC) return { enabled: [], disabled: [] };
|
||||
if (EXPO_NO_CAPABILITY_SYNC) {
|
||||
return { enabled: [], disabled: [] };
|
||||
}
|
||||
const currentCapabilities = await bundleId.getBundleIdCapabilitiesAsync();
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Current remote capabilities:\n${JSON.stringify(currentCapabilities, null, 2)}`);
|
||||
@@ -107,7 +119,7 @@ function getCapabilitiesToEnable(
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingIndex = currentCapabilities.findIndex(existing =>
|
||||
const existingIndex = remainingCapabilities.findIndex(existing =>
|
||||
existing.isType(staticCapabilityInfo.capability)
|
||||
);
|
||||
const existing = existingIndex > -1 ? remainingCapabilities[existingIndex] : null;
|
||||
@@ -120,16 +132,19 @@ function getCapabilitiesToEnable(
|
||||
remainingCapabilities.splice(existingIndex, 1);
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Skipping existing capability: ${key} (${staticCapabilityInfo.name})`);
|
||||
Log.log(
|
||||
`Remaining to remove: `,
|
||||
remainingCapabilities.map(({ id }) => id)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!staticCapabilityInfo.validateOptions(value)) {
|
||||
throw new Error(`iOS entitlement "${key}" has invalid value "${value}".`);
|
||||
}
|
||||
assertValidOptions(staticCapabilityInfo, value);
|
||||
|
||||
enabledCapabilityNames.push(staticCapabilityInfo.name);
|
||||
|
||||
const option = staticCapabilityInfo.getOptions(value!, entitlements);
|
||||
const option = staticCapabilityInfo.getOptions(value, entitlements);
|
||||
|
||||
request.push({
|
||||
capabilityType: staticCapabilityInfo.capability,
|
||||
@@ -140,11 +155,30 @@ function getCapabilitiesToEnable(
|
||||
return { enabledCapabilityNames, request, remainingCapabilities };
|
||||
}
|
||||
|
||||
export function assertValidOptions(classifier: CapabilityClassifier, value: any): asserts value {
|
||||
if (!classifier.validateOptions(value)) {
|
||||
let reason = '';
|
||||
if (classifier.capabilityIdPrefix) {
|
||||
// Assert string array matching prefix. ASC will throw if the IDs are invalid, this just saves some time.
|
||||
reason = ` Expected an array of strings, where each string is prefixed with "${classifier.capabilityIdPrefix}", ex: ["${classifier.capabilityIdPrefix}myapp"]`;
|
||||
}
|
||||
throw new Error(
|
||||
`iOS entitlement "${classifier.entitlement}" has invalid value "${value}".${reason}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getCapabilitiesToDisable(
|
||||
bundleId: BundleId,
|
||||
currentCapabilities: BundleIdCapability[],
|
||||
request: { capabilityType: CapabilityType; option: any }[]
|
||||
) {
|
||||
if (Log.isDebug) {
|
||||
Log.log(
|
||||
`Existing to disable: `,
|
||||
currentCapabilities.map(({ id }) => id)
|
||||
);
|
||||
}
|
||||
const disabledCapabilityNames: string[] = [];
|
||||
|
||||
// Disable any extras that aren't present, this functionality is kinda unreliable because managed apps
|
||||
@@ -192,17 +226,21 @@ function getCapabilitiesToDisable(
|
||||
return { disabledCapabilityNames, request };
|
||||
}
|
||||
|
||||
// NOTE(Bacon): From manually toggling values in Xcode and checking the git diff and network requests.
|
||||
// Last Updated: May 5th, 2021
|
||||
// https://developer.apple.com/documentation/bundleresources/entitlements
|
||||
export const CapabilityMapping: {
|
||||
type CapabilityClassifier = {
|
||||
name: string;
|
||||
entitlement: string;
|
||||
capability: CapabilityType;
|
||||
validateOptions: (options: any) => boolean;
|
||||
getOptions: GetOptionsMethod;
|
||||
capabilityIdModel?: typeof MerchantId;
|
||||
capabilityIdPrefix?: string;
|
||||
options?: undefined;
|
||||
}[] = [
|
||||
};
|
||||
|
||||
// NOTE(Bacon): From manually toggling values in Xcode and checking the git diff and network requests.
|
||||
// Last Updated: July 22nd, 2021
|
||||
// https://developer.apple.com/documentation/bundleresources/entitlements
|
||||
export const CapabilityMapping: CapabilityClassifier[] = [
|
||||
{
|
||||
name: 'HomeKit',
|
||||
entitlement: 'com.apple.developer.homekit',
|
||||
@@ -287,24 +325,30 @@ export const CapabilityMapping: {
|
||||
entitlement: 'com.apple.security.application-groups',
|
||||
capability: CapabilityType.APP_GROUP,
|
||||
// Ex: ['group.CY-A5149AC2-49FC-11E7-B3F3-0335A16FFB8D.com.cydia.Extender']
|
||||
validateOptions: validateStringArrayOptions,
|
||||
validateOptions: validatePrefixedStringArrayOptions('group.'),
|
||||
getOptions: getDefinedOptions,
|
||||
capabilityIdModel: AppGroup,
|
||||
capabilityIdPrefix: 'group.',
|
||||
},
|
||||
{
|
||||
name: 'Apple Pay Payment Processing',
|
||||
entitlement: 'com.apple.developer.in-app-payments',
|
||||
capability: CapabilityType.APPLE_PAY,
|
||||
// Ex: ['merchant.com.example.development']
|
||||
validateOptions: validateStringArrayOptions,
|
||||
validateOptions: validatePrefixedStringArrayOptions('merchant.'),
|
||||
getOptions: getDefinedOptions,
|
||||
capabilityIdModel: MerchantId,
|
||||
capabilityIdPrefix: 'merchant.',
|
||||
},
|
||||
{
|
||||
name: 'iCloud',
|
||||
entitlement: 'com.apple.developer.icloud-container-identifiers',
|
||||
capability: CapabilityType.ICLOUD,
|
||||
validateOptions: validateStringArrayOptions,
|
||||
validateOptions: validatePrefixedStringArrayOptions('iCloud.'),
|
||||
// Only supports Xcode +6, 5 could be added if needed.
|
||||
getOptions: getDefinedOptions,
|
||||
capabilityIdModel: CloudContainer,
|
||||
capabilityIdPrefix: 'iCloud.',
|
||||
},
|
||||
{
|
||||
name: 'ClassKit',
|
||||
@@ -313,6 +357,34 @@ export const CapabilityMapping: {
|
||||
validateOptions: validateDevProdString,
|
||||
getOptions: getDefinedOptions,
|
||||
},
|
||||
{
|
||||
name: 'Communication Notifications',
|
||||
entitlement: 'com.apple.developer.usernotifications.communication',
|
||||
capability: CapabilityType.USER_NOTIFICATIONS_COMMUNICATION,
|
||||
validateOptions: validateBooleanOptions,
|
||||
getOptions: getBooleanOptions,
|
||||
},
|
||||
{
|
||||
name: 'Time Sensitive Notifications',
|
||||
entitlement: 'com.apple.developer.usernotifications.time-sensitive',
|
||||
capability: CapabilityType.USER_NOTIFICATIONS_TIME_SENSITIVE,
|
||||
validateOptions: validateBooleanOptions,
|
||||
getOptions: getBooleanOptions,
|
||||
},
|
||||
{
|
||||
name: 'Group Activities',
|
||||
entitlement: 'com.apple.developer.group-session',
|
||||
capability: CapabilityType.GROUP_ACTIVITIES,
|
||||
validateOptions: validateBooleanOptions,
|
||||
getOptions: getBooleanOptions,
|
||||
},
|
||||
{
|
||||
name: 'Family Controls',
|
||||
entitlement: 'com.apple.developer.family-controls',
|
||||
capability: CapabilityType.FAMILY_CONTROLS,
|
||||
validateOptions: validateBooleanOptions,
|
||||
getOptions: getBooleanOptions,
|
||||
},
|
||||
{
|
||||
// https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_default-data-protection
|
||||
name: 'Data Protection',
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { BundleId, CapabilityTypeOption } from '@expo/apple-utils';
|
||||
import { JSONObject } from '@expo/json-file';
|
||||
|
||||
import Log from '../../../log';
|
||||
import {
|
||||
CapabilityMapping,
|
||||
EXPO_NO_CAPABILITY_SYNC,
|
||||
assertValidOptions,
|
||||
} from './bundleIdCapabilities';
|
||||
|
||||
type UpdateCapabilityRequest = Parameters<BundleId['updateBundleIdCapabilityAsync']>[0];
|
||||
|
||||
/**
|
||||
* Sync the capability identifiers with the bundle identifier capabilities.
|
||||
* 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.
|
||||
*
|
||||
* @param bundleId Bundle identifier object.
|
||||
* @param entitlements JSON representation of the iOS entitlements plist
|
||||
*
|
||||
* @returns an object specifying the capabilities that were linked, and created.
|
||||
*/
|
||||
export async function syncCapabilityIdentifiersForEntitlementsAsync(
|
||||
bundleId: BundleId,
|
||||
entitlements: JSONObject = {}
|
||||
): Promise<{ created: string[]; linked: string[] }> {
|
||||
if (EXPO_NO_CAPABILITY_SYNC) {
|
||||
return { created: [], linked: [] };
|
||||
}
|
||||
|
||||
const createdIds: string[] = [];
|
||||
const linkedIds: string[] = [];
|
||||
const CapabilityIdMapping = CapabilityMapping.filter(capability => capability.capabilityIdModel);
|
||||
|
||||
const updateRequest: UpdateCapabilityRequest = [];
|
||||
|
||||
// Iterate through the supported capabilities to build the request.
|
||||
for (const classifier of CapabilityIdMapping) {
|
||||
const CapabilityModel = classifier.capabilityIdModel;
|
||||
// Skip capabilities that don't support capability IDs.
|
||||
if (!CapabilityModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const validate = (value: any): value is string[] => {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
assertValidOptions(classifier, value);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Skip capabilities that aren't defined in the entitlements file.
|
||||
const entitlementValue = entitlements[classifier.entitlement];
|
||||
|
||||
if (!validate(entitlementValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove any duplicates to cut down on network requests
|
||||
const capabilityIds: string[] = [...new Set(entitlementValue)];
|
||||
|
||||
// Get a list of all of the capability IDs that are already created on the server.
|
||||
const existingIds = await CapabilityModel.getAsync(bundleId.context);
|
||||
|
||||
// A list of server IDs for linking.
|
||||
const capabilityIdOpaqueIds = [];
|
||||
|
||||
// Iterate through all the local IDs and see if they exist on the server.
|
||||
for (const localId of capabilityIds) {
|
||||
let remoteIdModel = existingIds.find(model => model.attributes.identifier === localId);
|
||||
|
||||
// If a remote ID exists, then create it.
|
||||
if (!remoteIdModel) {
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Creating capability ID: ${localId} (${CapabilityModel.type})`);
|
||||
}
|
||||
try {
|
||||
remoteIdModel = await CapabilityModel.createAsync(bundleId.context, {
|
||||
identifier: localId,
|
||||
});
|
||||
} catch (error) {
|
||||
// Add a more helpful error message.
|
||||
error.message += `\n\nRemove the value '${localId}' from the array '${classifier.entitlement}' in the iOS project entitlements.\nIf you know that the ID is registered to one of your apps, try again with a different Apple account.`;
|
||||
throw error;
|
||||
}
|
||||
// Create a list of newly created IDs for displaying in the CLI.
|
||||
createdIds.push(localId);
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Created capability ID: ${remoteIdModel.id}`);
|
||||
}
|
||||
}
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Linking ID to ${CapabilityModel.type}: ${localId} (${remoteIdModel.id})`);
|
||||
}
|
||||
// Create a list of linked IDs for displaying in the CLI.
|
||||
linkedIds.push(remoteIdModel.attributes.identifier);
|
||||
capabilityIdOpaqueIds.push(remoteIdModel.id);
|
||||
}
|
||||
|
||||
updateRequest.push({
|
||||
capabilityType: classifier.capability,
|
||||
option: CapabilityTypeOption.ON,
|
||||
relationships: {
|
||||
// One of: `merchantIds`, `appGroups`, `cloudContainers`.
|
||||
[CapabilityModel.type]: capabilityIdOpaqueIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (updateRequest.length) {
|
||||
if (Log.isDebug) {
|
||||
Log.log(`Updating bundle identifier with capability identifiers:`, updateRequest);
|
||||
}
|
||||
await bundleId.updateBundleIdCapabilityAsync(updateRequest);
|
||||
} else if (Log.isDebug) {
|
||||
Log.log(`No capability identifiers need to be updated`);
|
||||
}
|
||||
return { created: createdIds, linked: linkedIds };
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import ora from 'ora';
|
||||
import Log from '../../../log';
|
||||
import { AuthCtx, getRequestContext } from './authenticate';
|
||||
import { syncCapabilitiesForEntitlementsAsync } from './bundleIdCapabilities';
|
||||
import { syncCapabilityIdentifiersForEntitlementsAsync } from './capabilityIdentifiers';
|
||||
import { assertContractMessagesAsync } from './contractMessages';
|
||||
|
||||
export interface IosCapabilitiesOptions {
|
||||
@@ -95,8 +96,6 @@ export async function syncCapabilities(
|
||||
bundleId,
|
||||
entitlements
|
||||
);
|
||||
const buildMessage = (title: string, items: string[]) =>
|
||||
items.length ? `${title}: ${items.join(', ')}` : '';
|
||||
const results =
|
||||
[buildMessage('Enabled', enabled), buildMessage('Disabled', disabled)]
|
||||
.filter(Boolean)
|
||||
@@ -107,6 +106,43 @@ export async function syncCapabilities(
|
||||
spinner.fail(`Failed to sync capabilities ${chalk.dim(bundleId.attributes.identifier)}`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Always run this after syncing the capabilities...
|
||||
await syncCapabilityIdentifiersAsync(bundleId, { entitlements });
|
||||
}
|
||||
|
||||
const buildMessage = (title: string, items: string[]) =>
|
||||
items.length ? `${title}: ${items.join(', ')}` : '';
|
||||
|
||||
export async function syncCapabilityIdentifiersAsync(
|
||||
bundleId: BundleId,
|
||||
{ entitlements }: IosCapabilitiesOptions
|
||||
): Promise<void> {
|
||||
const spinner = ora(`Syncing capabilities identifiers`).start();
|
||||
|
||||
// Stop spinning in debug mode so we can print other information
|
||||
if (Log.isDebug) {
|
||||
spinner.stop();
|
||||
}
|
||||
|
||||
try {
|
||||
const { created, linked } = await syncCapabilityIdentifiersForEntitlementsAsync(
|
||||
bundleId,
|
||||
entitlements
|
||||
);
|
||||
|
||||
const results =
|
||||
[buildMessage('Created', created), buildMessage('Linked', linked)]
|
||||
.filter(Boolean)
|
||||
.join(' | ') || 'No updates';
|
||||
|
||||
spinner.succeed(`Synced capability identifiers: ` + chalk.dim(results));
|
||||
} catch (err) {
|
||||
spinner.fail(
|
||||
`Failed to sync capability identifiers ${chalk.dim(bundleId.attributes.identifier)}`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureAppExistsAsync(
|
||||
|
||||
@@ -136,10 +136,11 @@ async function manageAdHocProfilesAsync(
|
||||
if (profileId) {
|
||||
existingProfile = await findProfileByIdAsync(context, profileId, bundleId);
|
||||
// Fail if we cannot find the profile that was specifically requested
|
||||
if (!existingProfile)
|
||||
if (!existingProfile) {
|
||||
throw new Error(
|
||||
`Could not find profile with profile id "${profileId}" for bundle id "${bundleId}"`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If no profile id is passed, try to find a suitable provisioning profile for the App ID.
|
||||
const results = await findProfileByBundleIdAsync(context, bundleId, certSerialNumber);
|
||||
|
||||
@@ -41,7 +41,9 @@ function getService(rootUrl: string) {
|
||||
},
|
||||
replacement(content: string, node: any) {
|
||||
let href = node.getAttribute('href');
|
||||
if (href.startsWith('/')) href = `${rootUrl}${href}`;
|
||||
if (href.startsWith('/')) {
|
||||
href = `${rootUrl}${href}`;
|
||||
}
|
||||
if (terminalLink.isSupported) {
|
||||
return chalk.cyan(terminalLink(content, href));
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function displayIosCredentials(
|
||||
}
|
||||
Log.log(` Bundle Identifier: ${chalk.bold(bundleIdentifier)}`);
|
||||
const targetAppCredentials = appCredentialsMap[targetName];
|
||||
if (!targetAppCredentials || targetAppCredentials.iosAppBuildCredentialsList.length === 0) {
|
||||
if (!targetAppCredentials) {
|
||||
Log.newLine();
|
||||
Log.log(` No credentials set up yet!`);
|
||||
Log.newLine();
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import Log from '../../log';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
import assert from 'assert';
|
||||
|
||||
import Log, { learnMore } from '../../log';
|
||||
import {
|
||||
getProjectAccountName,
|
||||
promptToCreateProjectIfNotExistsAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { findAccountByName } from '../../user/Account';
|
||||
import { ensureActorHasUsername } from '../../user/actions';
|
||||
import { AssignFcm } from '../android/actions/AssignFcm';
|
||||
import {
|
||||
canCopyLegacyCredentialsAsync,
|
||||
getAppLookupParamsFromContext,
|
||||
getAppLookupParamsFromContextAsync,
|
||||
promptUserAndCopyLegacyCredentialsAsync,
|
||||
} from '../android/actions/BuildCredentialsUtils';
|
||||
import { CreateFcm } from '../android/actions/CreateFcm';
|
||||
@@ -147,7 +152,13 @@ export class ManageAndroid {
|
||||
throw new Error(`You do not have access to account: ${accountName}`);
|
||||
}
|
||||
if (ctx.hasProjectContext) {
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
|
||||
if (!maybeProjectId) {
|
||||
throw new Error(
|
||||
'Your project must be registered with EAS in order to use the credentials manager.'
|
||||
);
|
||||
}
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync(
|
||||
appLookupParams
|
||||
);
|
||||
@@ -196,61 +207,74 @@ export class ManageAndroid {
|
||||
return await this.callingAction.runAsync(ctx);
|
||||
}
|
||||
}
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
if (chosenAction === ActionType.CreateKeystore) {
|
||||
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
const keystore = await new CreateKeystore(appLookupParams.account).runAsync(ctx);
|
||||
if (
|
||||
selectBuildCredentialsResult.resultType ===
|
||||
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
|
||||
) {
|
||||
await ctx.android.createAndroidAppBuildCredentialsAsync(appLookupParams, {
|
||||
...selectBuildCredentialsResult.result,
|
||||
androidKeystoreId: keystore.id,
|
||||
});
|
||||
} else {
|
||||
await ctx.android.updateAndroidAppBuildCredentialsAsync(
|
||||
selectBuildCredentialsResult.result,
|
||||
{
|
||||
androidKeystoreId: keystore.id,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (chosenAction === ActionType.DownloadKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new DownloadKeystore({ app: appLookupParams }).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (chosenAction === ActionType.RemoveKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new RemoveKeystore(appLookupParams).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (chosenAction === ActionType.CreateFcm) {
|
||||
const fcm = await new CreateFcm(appLookupParams.account).runAsync(ctx);
|
||||
await new AssignFcm(appLookupParams).runAsync(ctx, fcm);
|
||||
} else if (chosenAction === ActionType.RemoveFcm) {
|
||||
await new RemoveFcm(appLookupParams).runAsync(ctx);
|
||||
} else if (chosenAction === ActionType.UpdateCredentialsJson) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new UpdateCredentialsJson().runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (chosenAction === ActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(appLookupParams).runAsync(ctx);
|
||||
}
|
||||
|
||||
await this.runProjectSpecificActionAsync(ctx, chosenAction);
|
||||
} catch (err) {
|
||||
Log.error(err);
|
||||
if (process.env.DEBUG) {
|
||||
throw err; // breaks out of the loop so we can get stack trace
|
||||
}
|
||||
Log.error(`Cryptic error? ${learnMore('https://expo.fyi/cryptic-error-eas')}`);
|
||||
}
|
||||
await new PressAnyKeyToContinue().runAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async runProjectSpecificActionAsync(ctx: Context, action: ActionType): Promise<void> {
|
||||
assert(
|
||||
ctx.hasProjectContext,
|
||||
'You must be in your project directory in order to perform this action'
|
||||
);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
if (action === ActionType.CreateKeystore) {
|
||||
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
const keystore = await new CreateKeystore(appLookupParams.account).runAsync(ctx);
|
||||
if (
|
||||
selectBuildCredentialsResult.resultType ===
|
||||
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
|
||||
) {
|
||||
await ctx.android.createAndroidAppBuildCredentialsAsync(appLookupParams, {
|
||||
...selectBuildCredentialsResult.result,
|
||||
androidKeystoreId: keystore.id,
|
||||
});
|
||||
} else {
|
||||
await ctx.android.updateAndroidAppBuildCredentialsAsync(
|
||||
selectBuildCredentialsResult.result,
|
||||
{
|
||||
androidKeystoreId: keystore.id,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (action === ActionType.DownloadKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new DownloadKeystore({ app: appLookupParams }).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.RemoveKeystore) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new RemoveKeystore(appLookupParams).runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.CreateFcm) {
|
||||
const fcm = await new CreateFcm(appLookupParams.account).runAsync(ctx);
|
||||
await new AssignFcm(appLookupParams).runAsync(ctx, fcm);
|
||||
} else if (action === ActionType.RemoveFcm) {
|
||||
await new RemoveFcm(appLookupParams).runAsync(ctx);
|
||||
} else if (action === ActionType.UpdateCredentialsJson) {
|
||||
const buildCredentials = await new SelectExistingAndroidBuildCredentials(
|
||||
appLookupParams
|
||||
).runAsync(ctx);
|
||||
if (buildCredentials) {
|
||||
await new UpdateCredentialsJson().runAsync(ctx, buildCredentials);
|
||||
}
|
||||
} else if (action === ActionType.SetupBuildCredentialsFromCredentialsJson) {
|
||||
await new SetupBuildCredentialsFromCredentialsJson(appLookupParams).runAsync(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,13 @@ import {
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType as IosDistributionTypeGraphql,
|
||||
} from '../../graphql/generated';
|
||||
import Log from '../../log';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { resolveXcodeBuildContextAsync } from '../../project/ios/scheme';
|
||||
import { resolveTargetsAsync } from '../../project/ios/target';
|
||||
import { getProjectAccountName } from '../../project/projectUtils';
|
||||
import {
|
||||
getProjectAccountName,
|
||||
promptToCreateProjectIfNotExistsAsync,
|
||||
} from '../../project/projectUtils';
|
||||
import { confirmAsync, promptAsync, selectAsync } from '../../prompts';
|
||||
import { Account, findAccountByName } from '../../user/Account';
|
||||
import { ensureActorHasUsername } from '../../user/actions';
|
||||
@@ -235,11 +238,15 @@ export class ManageIos {
|
||||
return await this.callingAction.runAsync(ctx);
|
||||
}
|
||||
} else if (actionInfo.scope === Scope.Project) {
|
||||
assert(
|
||||
ctx.hasProjectContext,
|
||||
'You must be in your project directory in order to perform this action'
|
||||
);
|
||||
await this.runProjectSpecificActionAsync(
|
||||
ctx,
|
||||
nullthrows(app),
|
||||
nullthrows(targets),
|
||||
nullthrows(buildProfile),
|
||||
nullthrows(app, 'app must be defined in project context'),
|
||||
nullthrows(targets, 'targets must be defined in project context'),
|
||||
nullthrows(buildProfile, 'buildProfile must be defined in project context'),
|
||||
chosenAction
|
||||
);
|
||||
} else if (actionInfo.scope === Scope.Account) {
|
||||
@@ -249,6 +256,10 @@ export class ManageIos {
|
||||
}
|
||||
} catch (err) {
|
||||
Log.error(err);
|
||||
if (process.env.DEBUG) {
|
||||
throw err; // breaks out of the loop so we can get stack trace
|
||||
}
|
||||
Log.error(`Cryptic error? ${learnMore('https://expo.fyi/cryptic-error-eas')}`);
|
||||
}
|
||||
await new PressAnyKeyToContinue().runAsync();
|
||||
}
|
||||
@@ -266,6 +277,13 @@ export class ManageIos {
|
||||
};
|
||||
}
|
||||
|
||||
const maybeProjectId = await promptToCreateProjectIfNotExistsAsync(ctx.exp);
|
||||
if (!maybeProjectId) {
|
||||
throw new Error(
|
||||
'Your project must be registered with EAS in order to use the credentials manager.'
|
||||
);
|
||||
}
|
||||
|
||||
const app = { account, projectName: ctx.exp.slug };
|
||||
const easJsonReader = new EasJsonReader(ctx.projectDir, 'ios');
|
||||
const easConfig = await new SelectBuildProfileFromEasJson(easJsonReader).runAsync(ctx);
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import {
|
||||
testAndroidBuildCredentialsFragment,
|
||||
} from '../../__tests__/fixtures-android';
|
||||
import { createCtxMock } from '../../__tests__/fixtures-context';
|
||||
import { getAppLookupParamsFromContext } from '../../android/actions/BuildCredentialsUtils';
|
||||
import { getAppLookupParamsFromContextAsync } from '../../android/actions/BuildCredentialsUtils';
|
||||
import {
|
||||
SelectAndroidBuildCredentials,
|
||||
SelectAndroidBuildCredentialsResultType,
|
||||
@@ -28,7 +28,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
getAndroidAppBuildCredentialsListAsync: jest.fn(() => []),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
|
||||
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
|
||||
expect(buildCredentialsMetadataInput).toMatchObject({
|
||||
@@ -50,7 +50,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
]),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
|
||||
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
|
||||
expect(buildCredentialsMetadataInput).toMatchObject({
|
||||
@@ -70,7 +70,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
getAndroidAppBuildCredentialsListAsync: jest.fn(() => []),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
|
||||
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
|
||||
expect(buildCredentialsMetadataInput).toMatchObject({
|
||||
@@ -90,7 +90,7 @@ describe(SelectAndroidBuildCredentials, () => {
|
||||
]),
|
||||
},
|
||||
});
|
||||
const appLookupParams = getAppLookupParamsFromContext(ctx);
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
|
||||
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
|
||||
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
|
||||
expect(buildCredentialsMetadataInput).toMatchObject({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import prompts from 'prompts';
|
||||
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import Log from '../../log';
|
||||
import { Actor } from '../../user/User';
|
||||
import { AccountResolver } from '../manager';
|
||||
|
||||
@@ -69,15 +70,11 @@ describe(AccountResolver, () => {
|
||||
accounts: [user.accounts[0]],
|
||||
};
|
||||
|
||||
const originalConsoleWarn = console.warn;
|
||||
console.warn = jest.fn();
|
||||
|
||||
jest.spyOn(Log, 'warn');
|
||||
const resolver = new AccountResolver(exp, userWithAccessToProjectAccount);
|
||||
const account = await resolver.resolveAccountAsync();
|
||||
expect(account).toEqual(user.accounts[0]);
|
||||
expect(console.warn).toBeCalledWith(expect.stringMatching(/doesn't have access to the/));
|
||||
|
||||
console.warn = originalConsoleWarn;
|
||||
expect(Log.warn).toBeCalledWith(expect.stringMatching(/doesn't have access to the/));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,14 +6,6 @@ import DeviceCreateAction, { RegistrationMethod } from '../action';
|
||||
import { runInputMethodAsync } from '../inputMethod';
|
||||
import { runRegistrationUrlMethodAsync } from '../registrationUrlMethod';
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
});
|
||||
|
||||
jest.mock('prompts');
|
||||
jest.mock('../registrationUrlMethod');
|
||||
jest.mock('../inputMethod');
|
||||
|
||||
@@ -8,17 +8,6 @@ import { runInputMethodAsync } from '../inputMethod';
|
||||
jest.mock('prompts');
|
||||
jest.mock('../../../../credentials/ios/api/graphql/mutations/AppleDeviceMutation');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
asMock(prompts).mockReset();
|
||||
asMock(prompts).mockImplementation(() => {
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function runRegistrationUrlMethodAsync(
|
||||
): Promise<void> {
|
||||
const registrationURL = await generateDeviceRegistrationURLAsync(accountId, appleTeam);
|
||||
Log.newLine();
|
||||
qrcodeTerminal.generate(registrationURL, code => console.log(`${indentString(code, 2)}\n`));
|
||||
qrcodeTerminal.generate(registrationURL, code => Log.log(`${indentString(code, 2)}\n`));
|
||||
Log.log(
|
||||
'Open the following link on your iOS devices (or scan the QR code) and follow the instructions to install the development profile:'
|
||||
);
|
||||
|
||||
+275
-205
@@ -58,13 +58,11 @@ export type RootQuery = {
|
||||
buildJobs: BuildJobQuery;
|
||||
builds: BuildQuery;
|
||||
clientBuilds: ClientBuildQuery;
|
||||
/** Top-level query object for querying EAS Build configuration. */
|
||||
easBuild: EasBuildQuery;
|
||||
/** Top-level query object for querying Experimentation configuration. */
|
||||
experimentation: ExperimentationQuery;
|
||||
project: ProjectQuery;
|
||||
/** Search for Snacks */
|
||||
search: Array<SearchResult>;
|
||||
/** @deprecated Use 'search' root field. */
|
||||
searchUsersAndApps: Array<Maybe<SearchResult>>;
|
||||
snack: SnackQuery;
|
||||
submissions: SubmissionQuery;
|
||||
/** Top-level query object for querying UserInvitationPublicData publicly. */
|
||||
@@ -113,22 +111,6 @@ export type RootQueryAllPublicAppsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type RootQuerySearchArgs = {
|
||||
type: SearchType;
|
||||
query: Scalars['String'];
|
||||
offset: Scalars['Int'];
|
||||
limit: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
export type RootQuerySearchUsersAndAppsArgs = {
|
||||
type: SearchType;
|
||||
query: Scalars['String'];
|
||||
offset: Scalars['Int'];
|
||||
limit: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
export type RootQueryUserByUserIdArgs = {
|
||||
userId: Scalars['String'];
|
||||
};
|
||||
@@ -208,8 +190,13 @@ export type Account = {
|
||||
buildJobs: Array<BuildJob>;
|
||||
/** (EAS Build) Builds associated with this account */
|
||||
builds: Array<Build>;
|
||||
/** Coalesced Build (EAS) or BuildJob (Classic) items for this account. Use "createdBefore" to offset a query. */
|
||||
/**
|
||||
* Coalesced Build (EAS) or BuildJob (Classic) for all apps belonging to this account.
|
||||
* @deprecated Use activityTimelineProjectActivities with filterTypes instead
|
||||
*/
|
||||
buildOrBuildJobs: Array<BuildOrBuildJob>;
|
||||
/** Coalesced project activity for all apps belonging to this account. */
|
||||
activityTimelineProjectActivities: Array<ActivityTimelineProjectActivity>;
|
||||
/** Owning User of this account if personal account */
|
||||
owner?: Maybe<User>;
|
||||
/** Actors associated with this account and permissions they hold */
|
||||
@@ -299,6 +286,17 @@ export type AccountBuildOrBuildJobsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 AccountActivityTimelineProjectActivitiesArgs = {
|
||||
limit: Scalars['Int'];
|
||||
createdBefore?: Maybe<Scalars['DateTime']>;
|
||||
filterTypes?: Maybe<Array<ActivityTimelineProjectActivityType>>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An account is a container owning projects, credentials, billing and other organization
|
||||
* data and settings. Actors may own and be members of accounts.
|
||||
@@ -431,7 +429,10 @@ export type App = Project & {
|
||||
/** (EAS Build) Builds associated with this app */
|
||||
builds: Array<Build>;
|
||||
buildJobs: Array<BuildJob>;
|
||||
/** Coalesced Build (EAS) or BuildJob (Classic) items for this app. Use "createdBefore" to offset a query. */
|
||||
/**
|
||||
* Coalesced Build (EAS) or BuildJob (Classic) items for this app.
|
||||
* @deprecated Use activityTimelineProjectActivities with filterTypes instead
|
||||
*/
|
||||
buildOrBuildJobs: Array<BuildOrBuildJob>;
|
||||
/** EAS Submissions associated with this app */
|
||||
submissions: Array<Submission>;
|
||||
@@ -447,7 +448,7 @@ export type App = Project & {
|
||||
updateBranches: Array<UpdateBranch>;
|
||||
/** get an EAS branch owned by the app by name */
|
||||
updateBranchByName?: Maybe<UpdateBranch>;
|
||||
/** Coalesced project activity for an app. Use "createdBefore" to offset a query. */
|
||||
/** Coalesced project activity for an app */
|
||||
activityTimelineProjectActivities: Array<ActivityTimelineProjectActivity>;
|
||||
/** Environment secrets for an app */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
@@ -558,6 +559,7 @@ export type AppUpdateBranchByNameArgs = {
|
||||
export type AppActivityTimelineProjectActivitiesArgs = {
|
||||
limit: Scalars['Int'];
|
||||
createdBefore?: Maybe<Scalars['DateTime']>;
|
||||
filterTypes?: Maybe<Array<ActivityTimelineProjectActivityType>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -637,13 +639,16 @@ export type Build = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
appVersion?: Maybe<Scalars['String']>;
|
||||
appBuildVersion?: Maybe<Scalars['String']>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
runtimeVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
channel?: Maybe<Scalars['String']>;
|
||||
metrics?: Maybe<BuildMetrics>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
gitCommitHash?: Maybe<Scalars['String']>;
|
||||
error?: Maybe<BuildError>;
|
||||
submissions: Array<Submission>;
|
||||
};
|
||||
|
||||
export type BuildOrBuildJob = {
|
||||
@@ -685,6 +690,8 @@ export type User = Actor & {
|
||||
hasPendingUserInvitations: Scalars['Boolean'];
|
||||
/** Pending UserInvitations for this user. Only resolves for the viewer. */
|
||||
pendingUserInvitations: Array<UserInvitation>;
|
||||
/** Coalesced project activity for all apps belonging to all accounts this user belongs to. Only resolves for the viewer. */
|
||||
activityTimelineProjectActivities: Array<ActivityTimelineProjectActivity>;
|
||||
/**
|
||||
* Server feature gate values for this actor, optionally filtering by desired gates.
|
||||
* Only resolves for the viewer.
|
||||
@@ -722,6 +729,14 @@ export type UserAppsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents a human (not robot) actor. */
|
||||
export type UserActivityTimelineProjectActivitiesArgs = {
|
||||
limit: Scalars['Int'];
|
||||
createdBefore?: Maybe<Scalars['DateTime']>;
|
||||
filterTypes?: Maybe<Array<ActivityTimelineProjectActivityType>>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents a human (not robot) actor. */
|
||||
export type UserFeatureGatesArgs = {
|
||||
filter?: Maybe<Array<Scalars['String']>>;
|
||||
@@ -799,6 +814,13 @@ export enum Role {
|
||||
NotAdmin = 'NOT_ADMIN'
|
||||
}
|
||||
|
||||
export enum ActivityTimelineProjectActivityType {
|
||||
BuildJob = 'BUILD_JOB',
|
||||
Build = 'BUILD',
|
||||
Update = 'UPDATE',
|
||||
Submission = 'SUBMISSION'
|
||||
}
|
||||
|
||||
|
||||
export type BuildArtifacts = {
|
||||
__typename?: 'BuildArtifacts';
|
||||
@@ -818,6 +840,11 @@ export enum DistributionType {
|
||||
Simulator = 'SIMULATOR'
|
||||
}
|
||||
|
||||
export enum BuildIosEnterpriseProvisioning {
|
||||
Adhoc = 'ADHOC',
|
||||
Universal = 'UNIVERSAL'
|
||||
}
|
||||
|
||||
export type BuildError = {
|
||||
__typename?: 'BuildError';
|
||||
errorCode: Scalars['String'];
|
||||
@@ -825,6 +852,72 @@ export type BuildError = {
|
||||
docsUrl?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** Represents an EAS Submission */
|
||||
export type Submission = ActivityTimelineProjectActivity & {
|
||||
__typename?: 'Submission';
|
||||
id: Scalars['ID'];
|
||||
actor?: Maybe<Actor>;
|
||||
activityTimestamp: Scalars['DateTime'];
|
||||
app?: Maybe<App>;
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
submittedBuild?: Maybe<Build>;
|
||||
platform: AppPlatform;
|
||||
status: SubmissionStatus;
|
||||
androidConfig?: Maybe<AndroidSubmissionConfig>;
|
||||
iosConfig?: Maybe<IosSubmissionConfig>;
|
||||
logsUrl?: Maybe<Scalars['String']>;
|
||||
error?: Maybe<SubmissionError>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum SubmissionStatus {
|
||||
InQueue = 'IN_QUEUE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Finished = 'FINISHED',
|
||||
Errored = 'ERRORED'
|
||||
}
|
||||
|
||||
export type AndroidSubmissionConfig = {
|
||||
__typename?: 'AndroidSubmissionConfig';
|
||||
applicationIdentifier: Scalars['String'];
|
||||
archiveType: SubmissionAndroidArchiveType;
|
||||
track: SubmissionAndroidTrack;
|
||||
releaseStatus?: Maybe<SubmissionAndroidReleaseStatus>;
|
||||
};
|
||||
|
||||
export enum SubmissionAndroidArchiveType {
|
||||
Apk = 'APK',
|
||||
Aab = 'AAB'
|
||||
}
|
||||
|
||||
export enum SubmissionAndroidTrack {
|
||||
Production = 'PRODUCTION',
|
||||
Internal = 'INTERNAL',
|
||||
Alpha = 'ALPHA',
|
||||
Beta = 'BETA'
|
||||
}
|
||||
|
||||
export enum SubmissionAndroidReleaseStatus {
|
||||
Draft = 'DRAFT',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Halted = 'HALTED',
|
||||
Completed = 'COMPLETED'
|
||||
}
|
||||
|
||||
export type IosSubmissionConfig = {
|
||||
__typename?: 'IosSubmissionConfig';
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
appleIdUsername: Scalars['String'];
|
||||
appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type SubmissionError = {
|
||||
__typename?: 'SubmissionError';
|
||||
errorCode?: Maybe<Scalars['String']>;
|
||||
message?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** Represents an Standalone App build job */
|
||||
export type BuildJob = ActivityTimelineProjectActivity & BuildOrBuildJob & {
|
||||
__typename?: 'BuildJob';
|
||||
@@ -891,71 +984,6 @@ export type SubmissionFilter = {
|
||||
status?: Maybe<SubmissionStatus>;
|
||||
};
|
||||
|
||||
export enum SubmissionStatus {
|
||||
InQueue = 'IN_QUEUE',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Finished = 'FINISHED',
|
||||
Errored = 'ERRORED'
|
||||
}
|
||||
|
||||
/** Represents an EAS Submission */
|
||||
export type Submission = ActivityTimelineProjectActivity & {
|
||||
__typename?: 'Submission';
|
||||
id: Scalars['ID'];
|
||||
actor?: Maybe<Actor>;
|
||||
activityTimestamp: Scalars['DateTime'];
|
||||
app?: Maybe<App>;
|
||||
initiatingActor?: Maybe<Actor>;
|
||||
platform: AppPlatform;
|
||||
status: SubmissionStatus;
|
||||
androidConfig?: Maybe<AndroidSubmissionConfig>;
|
||||
iosConfig?: Maybe<IosSubmissionConfig>;
|
||||
logsUrl?: Maybe<Scalars['String']>;
|
||||
error?: Maybe<SubmissionError>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type AndroidSubmissionConfig = {
|
||||
__typename?: 'AndroidSubmissionConfig';
|
||||
applicationIdentifier: Scalars['String'];
|
||||
archiveType: SubmissionAndroidArchiveType;
|
||||
track: SubmissionAndroidTrack;
|
||||
releaseStatus?: Maybe<SubmissionAndroidReleaseStatus>;
|
||||
};
|
||||
|
||||
export enum SubmissionAndroidArchiveType {
|
||||
Apk = 'APK',
|
||||
Aab = 'AAB'
|
||||
}
|
||||
|
||||
export enum SubmissionAndroidTrack {
|
||||
Production = 'PRODUCTION',
|
||||
Internal = 'INTERNAL',
|
||||
Alpha = 'ALPHA',
|
||||
Beta = 'BETA'
|
||||
}
|
||||
|
||||
export enum SubmissionAndroidReleaseStatus {
|
||||
Draft = 'DRAFT',
|
||||
InProgress = 'IN_PROGRESS',
|
||||
Halted = 'HALTED',
|
||||
Completed = 'COMPLETED'
|
||||
}
|
||||
|
||||
export type IosSubmissionConfig = {
|
||||
__typename?: 'IosSubmissionConfig';
|
||||
ascAppIdentifier: Scalars['String'];
|
||||
appleIdUsername: Scalars['String'];
|
||||
appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type SubmissionError = {
|
||||
__typename?: 'SubmissionError';
|
||||
errorCode?: Maybe<Scalars['String']>;
|
||||
message?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type IosAppCredentialsFilter = {
|
||||
appleAppIdentifierId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
@@ -1513,6 +1541,12 @@ export type BuildQuery = {
|
||||
* @deprecated Use App.builds instead
|
||||
*/
|
||||
allForApp: Array<Maybe<Build>>;
|
||||
/**
|
||||
* Get all builds.
|
||||
* By default, they are sorted from latest to oldest.
|
||||
* Available only for admin users.
|
||||
*/
|
||||
all: Array<Build>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1529,6 +1563,19 @@ export type BuildQueryAllForAppArgs = {
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildQueryAllArgs = {
|
||||
statuses?: Maybe<Array<BuildStatus>>;
|
||||
offset?: Maybe<Scalars['Int']>;
|
||||
limit?: Maybe<Scalars['Int']>;
|
||||
order?: Maybe<Order>;
|
||||
};
|
||||
|
||||
export enum Order {
|
||||
Desc = 'DESC',
|
||||
Asc = 'ASC'
|
||||
}
|
||||
|
||||
export type ClientBuildQuery = {
|
||||
__typename?: 'ClientBuildQuery';
|
||||
byId: ClientBuild;
|
||||
@@ -1550,6 +1597,24 @@ export type ClientBuild = {
|
||||
userId?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type EasBuildQuery = {
|
||||
__typename?: 'EasBuildQuery';
|
||||
/** Get EAS Build kill switches state */
|
||||
killSwitches: Array<EasBuildKillSwitch>;
|
||||
};
|
||||
|
||||
export type EasBuildKillSwitch = {
|
||||
__typename?: 'EasBuildKillSwitch';
|
||||
name: EasBuildKillSwitchName;
|
||||
value: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export enum EasBuildKillSwitchName {
|
||||
StopAcceptingBuilds = 'STOP_ACCEPTING_BUILDS',
|
||||
StopSchedulingBuilds = 'STOP_SCHEDULING_BUILDS',
|
||||
StopAcceptingNormalPriorityBuilds = 'STOP_ACCEPTING_NORMAL_PRIORITY_BUILDS'
|
||||
}
|
||||
|
||||
export type ExperimentationQuery = {
|
||||
__typename?: 'ExperimentationQuery';
|
||||
/** Get user experimentation config */
|
||||
@@ -1590,19 +1655,6 @@ export type ProjectQueryByPathsArgs = {
|
||||
paths?: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
};
|
||||
|
||||
export enum SearchType {
|
||||
All = 'ALL',
|
||||
Users = 'USERS',
|
||||
Apps = 'APPS',
|
||||
Snacks = 'SNACKS'
|
||||
}
|
||||
|
||||
/** Represents a search result for an app */
|
||||
export type SearchResult = {
|
||||
id: Scalars['ID'];
|
||||
rank?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
export type SnackQuery = {
|
||||
__typename?: 'SnackQuery';
|
||||
/**
|
||||
@@ -1742,6 +1794,7 @@ export type RootMutation = {
|
||||
userInvitation: UserInvitationMutation;
|
||||
/** Mutations that modify the currently authenticated User */
|
||||
me?: Maybe<MeMutation>;
|
||||
easBuildKillSwitch: EasBuildKillSwitchMutation;
|
||||
/** Mutations that modify an EmailSubscription */
|
||||
emailSubscription: EmailSubscriptionMutation;
|
||||
/** Mutations that create and delete EnvironmentSecrets */
|
||||
@@ -2352,14 +2405,24 @@ export type BuildMutation = {
|
||||
* @deprecated Use cancelBuild instead
|
||||
*/
|
||||
cancel: Build;
|
||||
/** Create an Android generic build */
|
||||
/**
|
||||
* Create an Android generic build
|
||||
* @deprecated Use createAndroidBuild instead
|
||||
*/
|
||||
createAndroidGenericBuild: CreateBuildResult;
|
||||
/** Create an Android managed build */
|
||||
/**
|
||||
* Create an Android managed build
|
||||
* @deprecated Use createAndroidBuild instead
|
||||
*/
|
||||
createAndroidManagedBuild: CreateBuildResult;
|
||||
/** Create an Android build */
|
||||
createAndroidBuild: CreateBuildResult;
|
||||
/** Create an iOS generic build */
|
||||
createIosGenericBuild: CreateBuildResult;
|
||||
/** Create an iOS managed build */
|
||||
createIosManagedBuild: CreateBuildResult;
|
||||
/** Create an iOS build */
|
||||
createIosBuild: CreateBuildResult;
|
||||
};
|
||||
|
||||
|
||||
@@ -2387,6 +2450,13 @@ export type BuildMutationCreateAndroidManagedBuildArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateAndroidBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosGenericBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosGenericJobInput;
|
||||
@@ -2400,6 +2470,13 @@ export type BuildMutationCreateIosManagedBuildArgs = {
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
|
||||
export type BuildMutationCreateIosBuildArgs = {
|
||||
appId: Scalars['ID'];
|
||||
job: IosJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
};
|
||||
|
||||
export type AndroidGenericJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
@@ -2468,9 +2545,11 @@ export type BuildMetadataInput = {
|
||||
workflow?: Maybe<BuildWorkflow>;
|
||||
credentialsSource?: Maybe<BuildCredentialsSource>;
|
||||
sdkVersion?: Maybe<Scalars['String']>;
|
||||
runtimeVersion?: Maybe<Scalars['String']>;
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
channel?: Maybe<Scalars['String']>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
iosEnterpriseProvisioning?: Maybe<BuildIosEnterpriseProvisioning>;
|
||||
appName?: Maybe<Scalars['String']>;
|
||||
appIdentifier?: Maybe<Scalars['String']>;
|
||||
buildProfile?: Maybe<Scalars['String']>;
|
||||
@@ -2523,6 +2602,27 @@ export enum AndroidManagedBuildType {
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type AndroidJobInput = {
|
||||
type: BuildWorkflow;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
secrets?: Maybe<AndroidJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<AndroidBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
gradleCommand?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<AndroidBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AndroidBuildType {
|
||||
Apk = 'APK',
|
||||
AppBundle = 'APP_BUNDLE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type IosGenericJobInput = {
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
@@ -2588,6 +2688,28 @@ export enum IosManagedBuildType {
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type IosJobInput = {
|
||||
type: BuildWorkflow;
|
||||
projectArchive: ProjectArchiveSourceInput;
|
||||
projectRootDirectory: Scalars['String'];
|
||||
releaseChannel?: Maybe<Scalars['String']>;
|
||||
updates?: Maybe<BuildUpdatesInput>;
|
||||
distribution?: Maybe<DistributionType>;
|
||||
secrets?: Maybe<IosJobSecretsInput>;
|
||||
builderEnvironment?: Maybe<IosBuilderEnvironmentInput>;
|
||||
cache?: Maybe<BuildCacheInput>;
|
||||
scheme?: Maybe<Scalars['String']>;
|
||||
buildConfiguration?: Maybe<Scalars['String']>;
|
||||
artifactPath?: Maybe<Scalars['String']>;
|
||||
buildType?: Maybe<IosBuildType>;
|
||||
username?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum IosBuildType {
|
||||
Release = 'RELEASE',
|
||||
DevelopmentClient = 'DEVELOPMENT_CLIENT'
|
||||
}
|
||||
|
||||
export type IosAppBuildCredentialsMutation = {
|
||||
__typename?: 'IosAppBuildCredentialsMutation';
|
||||
/** Create a set of build credentials for an iOS app */
|
||||
@@ -2740,6 +2862,7 @@ export type CreateSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
platform: AppPlatform;
|
||||
config: Scalars['JSONObject'];
|
||||
submittedBuildId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type CreateSubmissionResult = {
|
||||
@@ -2751,6 +2874,7 @@ export type CreateSubmissionResult = {
|
||||
export type CreateIosSubmissionInput = {
|
||||
appId: Scalars['ID'];
|
||||
config: NewIosSubmissionConfig;
|
||||
submittedBuildId?: Maybe<Scalars['ID']>;
|
||||
};
|
||||
|
||||
export type NewIosSubmissionConfig = {
|
||||
@@ -2832,7 +2956,7 @@ export type UpdateBranchMutation = {
|
||||
/** Delete an EAS branch and all of its updates as long as the branch is not being used by any channels */
|
||||
deleteUpdateBranch: DeleteUpdateBranchResult;
|
||||
/** Publish an update group to a branch */
|
||||
publishUpdateGroup: Array<Maybe<Update>>;
|
||||
publishUpdateGroup: Array<Update>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2884,6 +3008,7 @@ export type UpdateInfoGroup = {
|
||||
export type PartialManifest = {
|
||||
launchAsset: PartialManifestAsset;
|
||||
assets: Array<Maybe<PartialManifestAsset>>;
|
||||
extra?: Maybe<Scalars['JSONObject']>;
|
||||
};
|
||||
|
||||
export type PartialManifestAsset = {
|
||||
@@ -3167,6 +3292,20 @@ export type SecondFactorRegenerateBackupCodesResult = {
|
||||
plaintextBackupCodes: Array<Maybe<Scalars['String']>>;
|
||||
};
|
||||
|
||||
export type EasBuildKillSwitchMutation = {
|
||||
__typename?: 'EasBuildKillSwitchMutation';
|
||||
/** Set an EAS Build kill switch to a given value */
|
||||
set: EasBuildKillSwitch;
|
||||
/** Reset all EAS Build kill switches (set them to false) */
|
||||
resetAll: Array<EasBuildKillSwitch>;
|
||||
};
|
||||
|
||||
|
||||
export type EasBuildKillSwitchMutationSetArgs = {
|
||||
name: EasBuildKillSwitchName;
|
||||
value: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type EmailSubscriptionMutation = {
|
||||
__typename?: 'EmailSubscriptionMutation';
|
||||
addUser?: Maybe<AddUserPayload>;
|
||||
@@ -3283,44 +3422,6 @@ export type DeleteWebhookResult = {
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type BaseSearchResult = SearchResult & {
|
||||
__typename?: 'BaseSearchResult';
|
||||
/** @deprecated Use SearchResult instead */
|
||||
id: Scalars['ID'];
|
||||
/** @deprecated Use SearchResult instead */
|
||||
rank?: Maybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
/** Represents a search result for an app */
|
||||
export type AppSearchResult = SearchResult & {
|
||||
__typename?: 'AppSearchResult';
|
||||
/** @deprecated Field no longer supported */
|
||||
id: Scalars['ID'];
|
||||
/** @deprecated Field no longer supported */
|
||||
rank?: Maybe<Scalars['Int']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
app: App;
|
||||
};
|
||||
|
||||
/** Represents a search result for a user */
|
||||
export type UserSearchResult = SearchResult & {
|
||||
__typename?: 'UserSearchResult';
|
||||
/** @deprecated Field no longer supported */
|
||||
id: Scalars['ID'];
|
||||
/** @deprecated Field no longer supported */
|
||||
rank?: Maybe<Scalars['Int']>;
|
||||
/** @deprecated Field no longer supported */
|
||||
user: User;
|
||||
};
|
||||
|
||||
/** Represents a search result for a snack */
|
||||
export type SnackSearchResult = SearchResult & {
|
||||
__typename?: 'SnackSearchResult';
|
||||
id: Scalars['ID'];
|
||||
rank?: Maybe<Scalars['Int']>;
|
||||
snack: Snack;
|
||||
};
|
||||
|
||||
export enum CacheControlScope {
|
||||
Public = 'PUBLIC',
|
||||
Private = 'PRIVATE'
|
||||
@@ -3624,6 +3725,22 @@ export type GetChannelByNameForAppQuery = (
|
||||
)> }
|
||||
);
|
||||
|
||||
export type AppInfoQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AppInfoQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id' | 'fullName'>
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
export type DeleteUpdateGroupMutationVariables = Exact<{
|
||||
group: Scalars['ID'];
|
||||
}>;
|
||||
@@ -4497,18 +4614,18 @@ export type CreateAppMutation = (
|
||||
)> }
|
||||
);
|
||||
|
||||
export type CreateAndroidGenericBuildMutationVariables = Exact<{
|
||||
export type CreateAndroidBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidGenericJobInput;
|
||||
job: AndroidJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAndroidGenericBuildMutation = (
|
||||
export type CreateAndroidBuildMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { build?: Maybe<(
|
||||
{ __typename?: 'BuildMutation' }
|
||||
& { createAndroidGenericBuild: (
|
||||
& { createAndroidBuild: (
|
||||
{ __typename?: 'CreateBuildResult' }
|
||||
& { build: (
|
||||
{ __typename?: 'Build' }
|
||||
@@ -4521,66 +4638,18 @@ export type CreateAndroidGenericBuildMutation = (
|
||||
)> }
|
||||
);
|
||||
|
||||
export type CreateAndroidManagedBuildMutationVariables = Exact<{
|
||||
export type CreateIosBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: AndroidManagedJobInput;
|
||||
job: IosJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAndroidManagedBuildMutation = (
|
||||
export type CreateIosBuildMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { build?: Maybe<(
|
||||
{ __typename?: 'BuildMutation' }
|
||||
& { createAndroidManagedBuild: (
|
||||
{ __typename?: 'CreateBuildResult' }
|
||||
& { build: (
|
||||
{ __typename?: 'Build' }
|
||||
& Pick<Build, 'id'>
|
||||
), deprecationInfo?: Maybe<(
|
||||
{ __typename?: 'EASBuildDeprecationInfo' }
|
||||
& Pick<EasBuildDeprecationInfo, 'type' | 'message'>
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
export type CreateIosGenericBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: IosGenericJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateIosGenericBuildMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { build?: Maybe<(
|
||||
{ __typename?: 'BuildMutation' }
|
||||
& { createIosGenericBuild: (
|
||||
{ __typename?: 'CreateBuildResult' }
|
||||
& { build: (
|
||||
{ __typename?: 'Build' }
|
||||
& Pick<Build, 'id'>
|
||||
), deprecationInfo?: Maybe<(
|
||||
{ __typename?: 'EASBuildDeprecationInfo' }
|
||||
& Pick<EasBuildDeprecationInfo, 'type' | 'message'>
|
||||
)> }
|
||||
) }
|
||||
)> }
|
||||
);
|
||||
|
||||
export type CreateIosManagedBuildMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
job: IosManagedJobInput;
|
||||
metadata?: Maybe<BuildMetadataInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateIosManagedBuildMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { build?: Maybe<(
|
||||
{ __typename?: 'BuildMutation' }
|
||||
& { createIosManagedBuild: (
|
||||
& { createIosBuild: (
|
||||
{ __typename?: 'CreateBuildResult' }
|
||||
& { build: (
|
||||
{ __typename?: 'Build' }
|
||||
@@ -4670,10 +4739,10 @@ export type UpdatePublishMutation = (
|
||||
{ __typename?: 'RootMutation' }
|
||||
& { updateBranch: (
|
||||
{ __typename?: 'UpdateBranchMutation' }
|
||||
& { publishUpdateGroup: Array<Maybe<(
|
||||
& { publishUpdateGroup: Array<(
|
||||
{ __typename?: 'Update' }
|
||||
& Pick<Update, 'id' | 'group'>
|
||||
)>> }
|
||||
)> }
|
||||
) }
|
||||
);
|
||||
|
||||
@@ -4681,6 +4750,7 @@ export type CreateSubmissionMutationVariables = Exact<{
|
||||
appId: Scalars['ID'];
|
||||
platform: AppPlatform;
|
||||
config: Scalars['JSONObject'];
|
||||
submittedBuildId?: Maybe<Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -4826,16 +4896,16 @@ export type EnvironmentSecretsByAccountNameQuery = (
|
||||
) }
|
||||
);
|
||||
|
||||
export type EnvironmentSecretsByAppFullNameQueryVariables = Exact<{
|
||||
fullName: Scalars['String'];
|
||||
export type EnvironmentSecretsByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type EnvironmentSecretsByAppFullNameQuery = (
|
||||
export type EnvironmentSecretsByAppIdQuery = (
|
||||
{ __typename?: 'RootQuery' }
|
||||
& { app?: Maybe<(
|
||||
{ __typename?: 'AppQuery' }
|
||||
& { byFullName: (
|
||||
& { byId: (
|
||||
{ __typename?: 'App' }
|
||||
& Pick<App, 'id'>
|
||||
& { environmentSecrets: Array<(
|
||||
@@ -4993,7 +5063,7 @@ export type AppFragment = (
|
||||
|
||||
export type BuildFragment = (
|
||||
{ __typename?: 'Build' }
|
||||
& Pick<Build, 'id' | 'status' | 'platform' | 'releaseChannel' | 'distribution' | 'createdAt' | 'updatedAt'>
|
||||
& Pick<Build, 'id' | 'status' | 'platform' | 'channel' | 'releaseChannel' | 'distribution' | 'iosEnterpriseProvisioning' | 'buildProfile' | 'sdkVersion' | 'appVersion' | 'appBuildVersion' | 'runtimeVersion' | 'gitCommitHash' | 'createdAt' | 'updatedAt'>
|
||||
& { error?: Maybe<(
|
||||
{ __typename?: 'BuildError' }
|
||||
& Pick<BuildError, 'errorCode' | 'message' | 'docsUrl'>
|
||||
|
||||
@@ -3,20 +3,14 @@ import nullthrows from 'nullthrows';
|
||||
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
AndroidGenericJobInput,
|
||||
AndroidManagedJobInput,
|
||||
AndroidJobInput,
|
||||
BuildMetadataInput,
|
||||
CreateAndroidGenericBuildMutation,
|
||||
CreateAndroidGenericBuildMutationVariables,
|
||||
CreateAndroidManagedBuildMutation,
|
||||
CreateAndroidManagedBuildMutationVariables,
|
||||
CreateIosGenericBuildMutation,
|
||||
CreateIosGenericBuildMutationVariables,
|
||||
CreateIosManagedBuildMutation,
|
||||
CreateIosManagedBuildMutationVariables,
|
||||
CreateAndroidBuildMutation,
|
||||
CreateAndroidBuildMutationVariables,
|
||||
CreateIosBuildMutation,
|
||||
CreateIosBuildMutationVariables,
|
||||
EasBuildDeprecationInfo,
|
||||
IosGenericJobInput,
|
||||
IosManagedJobInput,
|
||||
IosJobInput,
|
||||
} from '../generated';
|
||||
|
||||
export interface BuildResult {
|
||||
@@ -25,22 +19,22 @@ export interface BuildResult {
|
||||
}
|
||||
|
||||
const BuildMutation = {
|
||||
async createAndroidGenericBuildAsync(input: {
|
||||
async createAndroidBuildAsync(input: {
|
||||
appId: string;
|
||||
job: AndroidGenericJobInput;
|
||||
job: AndroidJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateAndroidGenericBuildMutation, CreateAndroidGenericBuildMutationVariables>(
|
||||
.mutation<CreateAndroidBuildMutation, CreateAndroidBuildMutationVariables>(
|
||||
gql`
|
||||
mutation CreateAndroidGenericBuildMutation(
|
||||
mutation CreateAndroidBuildMutation(
|
||||
$appId: ID!
|
||||
$job: AndroidGenericJobInput!
|
||||
$job: AndroidJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
) {
|
||||
build {
|
||||
createAndroidGenericBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
createAndroidBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
build {
|
||||
id
|
||||
}
|
||||
@@ -56,24 +50,24 @@ const BuildMutation = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return nullthrows(data.build?.createAndroidGenericBuild);
|
||||
return nullthrows(data.build?.createAndroidBuild);
|
||||
},
|
||||
async createAndroidManagedBuildAsync(input: {
|
||||
async createIosBuildAsync(input: {
|
||||
appId: string;
|
||||
job: AndroidManagedJobInput;
|
||||
job: IosJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateAndroidManagedBuildMutation, CreateAndroidManagedBuildMutationVariables>(
|
||||
.mutation<CreateIosBuildMutation, CreateIosBuildMutationVariables>(
|
||||
gql`
|
||||
mutation CreateAndroidManagedBuildMutation(
|
||||
mutation CreateIosBuildMutation(
|
||||
$appId: ID!
|
||||
$job: AndroidManagedJobInput!
|
||||
$job: IosJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
) {
|
||||
build {
|
||||
createAndroidManagedBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
createIosBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
build {
|
||||
id
|
||||
}
|
||||
@@ -89,73 +83,7 @@ const BuildMutation = {
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return nullthrows(data.build?.createAndroidManagedBuild);
|
||||
},
|
||||
async createIosGenericBuildAsync(input: {
|
||||
appId: string;
|
||||
job: IosGenericJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateIosGenericBuildMutation, CreateIosGenericBuildMutationVariables>(
|
||||
gql`
|
||||
mutation CreateIosGenericBuildMutation(
|
||||
$appId: ID!
|
||||
$job: IosGenericJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
) {
|
||||
build {
|
||||
createIosGenericBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
build {
|
||||
id
|
||||
}
|
||||
deprecationInfo {
|
||||
type
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
input
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return nullthrows(data.build?.createIosGenericBuild);
|
||||
},
|
||||
async createIosManagedBuildAsync(input: {
|
||||
appId: string;
|
||||
job: IosManagedJobInput;
|
||||
metadata: BuildMetadataInput;
|
||||
}): Promise<BuildResult> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<CreateIosManagedBuildMutation, CreateIosManagedBuildMutationVariables>(
|
||||
gql`
|
||||
mutation CreateIosManagedBuildMutation(
|
||||
$appId: ID!
|
||||
$job: IosManagedJobInput!
|
||||
$metadata: BuildMetadataInput
|
||||
) {
|
||||
build {
|
||||
createIosManagedBuild(appId: $appId, job: $job, metadata: $metadata) {
|
||||
build {
|
||||
id
|
||||
}
|
||||
deprecationInfo {
|
||||
type
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
input
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return nullthrows(data.build?.createIosManagedBuild);
|
||||
return nullthrows(data.build?.createIosBuild);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ interface CreateSubmissionResult {
|
||||
export const SubmissionMutation = {
|
||||
async createSubmissionAsync(input: {
|
||||
appId: string;
|
||||
submittedBuildId?: string;
|
||||
platform: AppPlatform;
|
||||
config: JSONObject;
|
||||
}): Promise<CreateSubmissionResult> {
|
||||
@@ -28,9 +29,17 @@ export const SubmissionMutation = {
|
||||
$appId: ID!
|
||||
$platform: AppPlatform!
|
||||
$config: JSONObject!
|
||||
$submittedBuildId: ID
|
||||
) {
|
||||
submission {
|
||||
createSubmission(input: { appId: $appId, platform: $platform, config: $config }) {
|
||||
createSubmission(
|
||||
input: {
|
||||
appId: $appId
|
||||
platform: $platform
|
||||
config: $config
|
||||
submittedBuildId: $submittedBuildId
|
||||
}
|
||||
) {
|
||||
submission {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { print } from 'graphql';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { EnvironmentSecretScope } from '../../commands/secret/create';
|
||||
import { graphqlClient, withErrorHandlingAsync } from '../client';
|
||||
import {
|
||||
EnvironmentSecretFragment,
|
||||
EnvironmentSecretsByAccountNameQuery,
|
||||
EnvironmentSecretsByAppFullNameQuery,
|
||||
EnvironmentSecretsByAppIdQuery,
|
||||
} from '../generated';
|
||||
import { EnvironmentSecretFragmentNode } from '../types/EnvironmentSecret';
|
||||
|
||||
export enum EnvironmentSecretScope {
|
||||
ACCOUNT = 'account',
|
||||
PROJECT = 'project',
|
||||
}
|
||||
|
||||
export type EnvironmentSecretWithScope = EnvironmentSecretFragment & {
|
||||
scope: EnvironmentSecretScope;
|
||||
};
|
||||
@@ -40,14 +44,14 @@ export const EnvironmentSecretsQuery = {
|
||||
|
||||
return data.account.byName.environmentSecrets;
|
||||
},
|
||||
async byAppFullNameAsync(fullName: string): Promise<EnvironmentSecretFragment[]> {
|
||||
async byAppIdAsync(appId: string): Promise<EnvironmentSecretFragment[]> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<EnvironmentSecretsByAppFullNameQuery>(
|
||||
.query<EnvironmentSecretsByAppIdQuery>(
|
||||
gql`
|
||||
query EnvironmentSecretsByAppFullName($fullName: String!) {
|
||||
query EnvironmentSecretsByAppId($appId: String!) {
|
||||
app {
|
||||
byFullName(fullName: $fullName) {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
environmentSecrets {
|
||||
id
|
||||
@@ -58,12 +62,12 @@ export const EnvironmentSecretsQuery = {
|
||||
}
|
||||
${print(EnvironmentSecretFragmentNode)}
|
||||
`,
|
||||
{ fullName }
|
||||
{ appId }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
return data.app?.byFullName.environmentSecrets ?? [];
|
||||
return data.app?.byId.environmentSecrets ?? [];
|
||||
},
|
||||
async allAsync(
|
||||
projectAccountName: string,
|
||||
@@ -71,7 +75,7 @@ export const EnvironmentSecretsQuery = {
|
||||
): Promise<EnvironmentSecretWithScope[]> {
|
||||
const [accountSecrets, appSecrets] = await Promise.all([
|
||||
this.byAcccountNameAsync(projectAccountName),
|
||||
this.byAppFullNameAsync(projectFullName),
|
||||
this.byAppIdAsync(projectFullName),
|
||||
]);
|
||||
|
||||
return [
|
||||
|
||||
@@ -34,8 +34,16 @@ export const BuildFragmentNode = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
channel
|
||||
releaseChannel
|
||||
distribution
|
||||
iosEnterpriseProvisioning
|
||||
buildProfile
|
||||
sdkVersion
|
||||
appVersion
|
||||
appBuildVersion
|
||||
runtimeVersion
|
||||
gitCommitHash
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
||||
@@ -34,12 +34,16 @@ class CustomCommandHelp extends CommandHelp {
|
||||
this.aliases(cmd.aliases),
|
||||
this.examples(cmd.examples || (cmd as any).example),
|
||||
]).join('\n\n');
|
||||
if (this.opts.stripAnsi) output = stripAnsi(output);
|
||||
if (this.opts.stripAnsi) {
|
||||
output = stripAnsi(output);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
protected flags(flags: Config.Command.Flag[]): string | undefined {
|
||||
if (flags.length === 0) return;
|
||||
if (flags.length === 0) {
|
||||
return;
|
||||
}
|
||||
const groupableFlags = flags.map(originalFlag => {
|
||||
const { helpLabel, ...flag } = originalFlag;
|
||||
// We re-purpose `helpLabel` to mean a "header to group the flag under".
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import chalk from 'chalk';
|
||||
import figures from 'figures';
|
||||
import { boolish } from 'getenv';
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { asMock } from '../../__tests__/utils';
|
||||
import { confirmAsync } from '../../prompts';
|
||||
import { Actor, getUserAsync } from '../../user/User';
|
||||
import {
|
||||
ensureProjectExistsAsync,
|
||||
findProjectIdByAccountNameAndSlugNullableAsync,
|
||||
} from '../ensureProjectExists';
|
||||
import {
|
||||
findProjectRootAsync,
|
||||
getProjectAccountName,
|
||||
getProjectAccountNameAsync,
|
||||
promptToCreateProjectIfNotExistsAsync,
|
||||
} from '../projectUtils';
|
||||
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('../../user/User');
|
||||
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
jest.mock('../ensureProjectExists');
|
||||
|
||||
describe(findProjectRootAsync, () => {
|
||||
beforeEach(() => {
|
||||
@@ -168,3 +164,62 @@ describe(getProjectAccountNameAsync, () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe(promptToCreateProjectIfNotExistsAsync, () => {
|
||||
beforeEach(() => {
|
||||
asMock(getUserAsync).mockReset();
|
||||
asMock(findProjectIdByAccountNameAndSlugNullableAsync).mockReset();
|
||||
});
|
||||
|
||||
it('returns the existing project', async () => {
|
||||
asMock(getUserAsync).mockImplementation((): Actor | undefined => ({
|
||||
__typename: 'User',
|
||||
id: 'user_id',
|
||||
username: 'quin',
|
||||
accounts: [{ id: 'account_id_1', name: 'quin' }],
|
||||
isExpoAdmin: false,
|
||||
}));
|
||||
asMock(findProjectIdByAccountNameAndSlugNullableAsync).mockImplementation(
|
||||
() => 'already-existing-project-id'
|
||||
);
|
||||
const exp: any = {};
|
||||
const projectId = await promptToCreateProjectIfNotExistsAsync(exp);
|
||||
expect(projectId).toBe('already-existing-project-id');
|
||||
});
|
||||
it('makes a new project if none exists', async () => {
|
||||
jest.mock('../../prompts');
|
||||
asMock(getUserAsync).mockImplementation((): Actor | undefined => ({
|
||||
__typename: 'User',
|
||||
id: 'user_id',
|
||||
username: 'quin',
|
||||
accounts: [{ id: 'account_id_1', name: 'quin' }],
|
||||
isExpoAdmin: false,
|
||||
}));
|
||||
asMock(findProjectIdByAccountNameAndSlugNullableAsync).mockImplementation(() => null);
|
||||
asMock(ensureProjectExistsAsync).mockImplementation(() => 'new-project-id');
|
||||
|
||||
// user agrees to make new project
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => true);
|
||||
const exp: any = {};
|
||||
const projectId = await promptToCreateProjectIfNotExistsAsync(exp);
|
||||
expect(projectId).toBe('new-project-id');
|
||||
});
|
||||
it('aborts making a new project if the user declines', async () => {
|
||||
jest.mock('../../prompts');
|
||||
asMock(getUserAsync).mockImplementation((): Actor | undefined => ({
|
||||
__typename: 'User',
|
||||
id: 'user_id',
|
||||
username: 'quin',
|
||||
accounts: [{ id: 'account_id_1', name: 'quin' }],
|
||||
isExpoAdmin: false,
|
||||
}));
|
||||
asMock(findProjectIdByAccountNameAndSlugNullableAsync).mockImplementation(() => null);
|
||||
asMock(ensureProjectExistsAsync).mockImplementation(() => 'new-project-id');
|
||||
|
||||
// user doesnt agree to make new project
|
||||
(confirmAsync as jest.Mock).mockImplementation(() => false);
|
||||
const exp: any = {};
|
||||
const projectId = await promptToCreateProjectIfNotExistsAsync(exp);
|
||||
expect(projectId).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -155,22 +155,28 @@ describe(buildUpdateInfoGroupAsync, () => {
|
||||
|
||||
it('returns the correct value', async () => {
|
||||
await expect(
|
||||
buildUpdateInfoGroupAsync({
|
||||
android: {
|
||||
launchAsset: {
|
||||
type: 'bundle',
|
||||
contentType: 'bundle/javascript',
|
||||
path: androidBundlePath,
|
||||
},
|
||||
assets: [
|
||||
{
|
||||
type: 'jpg',
|
||||
contentType: 'image/jpeg',
|
||||
path: assetPath,
|
||||
buildUpdateInfoGroupAsync(
|
||||
{
|
||||
android: {
|
||||
launchAsset: {
|
||||
type: 'bundle',
|
||||
contentType: 'bundle/javascript',
|
||||
path: androidBundlePath,
|
||||
},
|
||||
],
|
||||
assets: [
|
||||
{
|
||||
type: 'jpg',
|
||||
contentType: 'image/jpeg',
|
||||
path: assetPath,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
{
|
||||
slug: 'hello',
|
||||
name: 'hello',
|
||||
}
|
||||
)
|
||||
).resolves.toEqual({
|
||||
android: {
|
||||
assets: [
|
||||
@@ -182,7 +188,6 @@ describe(buildUpdateInfoGroupAsync, () => {
|
||||
storageKey: 'fo8Y08LktVk6qLtGbn8GRWpOUyD13ABMUnbtRCN1L7Y',
|
||||
},
|
||||
],
|
||||
|
||||
launchAsset: {
|
||||
bundleKey: 'ec0dd14670aae108f99a810df9c1482c',
|
||||
contentType: 'bundle/javascript',
|
||||
@@ -190,6 +195,12 @@ describe(buildUpdateInfoGroupAsync, () => {
|
||||
storageBucket: 'update-assets-production',
|
||||
storageKey: 'aC9N6RZlcHoIYjIsoJd2KUcigBKy98RHvZacDyPNjCQ',
|
||||
},
|
||||
extra: {
|
||||
expoClient: {
|
||||
slug: 'hello',
|
||||
name: 'hello',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { resolveWorkflowAsync } from '../workflow';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe(resolveWorkflowAsync, () => {
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
const projectDir = '/app';
|
||||
|
||||
test('generic workflow for both platforms', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
projectDir
|
||||
);
|
||||
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.ANDROID)).resolves.toBe(
|
||||
Workflow.GENERIC
|
||||
);
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.IOS)).resolves.toBe(Workflow.GENERIC);
|
||||
});
|
||||
|
||||
test('generic workflow for single platform', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
},
|
||||
projectDir
|
||||
);
|
||||
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.ANDROID)).resolves.toBe(
|
||||
Workflow.MANAGED
|
||||
);
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.IOS)).resolves.toBe(Workflow.GENERIC);
|
||||
});
|
||||
|
||||
test('android/ios directories are ignored', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./ios/helloworld.xcodeproj/project.pbxproj': 'fake',
|
||||
'./android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
'./.easignore': 'android/\nios/',
|
||||
},
|
||||
projectDir
|
||||
);
|
||||
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.ANDROID)).resolves.toBe(
|
||||
Workflow.MANAGED
|
||||
);
|
||||
await expect(resolveWorkflowAsync(projectDir, Platform.IOS)).resolves.toBe(Workflow.MANAGED);
|
||||
});
|
||||
});
|
||||
@@ -3,19 +3,16 @@ import { vol } from 'memfs';
|
||||
import os from 'os';
|
||||
|
||||
import { asMock } from '../../../__tests__/utils';
|
||||
import { jester as mockJester } from '../../../credentials/__tests__/fixtures-constants';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import {
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync,
|
||||
getApplicationId,
|
||||
getApplicationIdAsync,
|
||||
} from '../applicationId';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../../../prompts');
|
||||
|
||||
const originalConsoleWarn = console.warn;
|
||||
beforeAll(() => {
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
jest.mock('../../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() => mockJester) }));
|
||||
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
@@ -26,71 +23,69 @@ beforeEach(() => {
|
||||
asMock(promptAsync).mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.removeSync(os.tmpdir());
|
||||
console.warn = originalConsoleWarn;
|
||||
});
|
||||
|
||||
describe(getApplicationId, () => {
|
||||
describe(getApplicationIdAsync, () => {
|
||||
describe('generic projects', () => {
|
||||
it('reads applicationId from build.gradle', () => {
|
||||
it('reads applicationId from build.gradle', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'android/app/build.gradle': `
|
||||
applicationId "com.expo.notdominik"
|
||||
`,
|
||||
'android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
|
||||
const applicationId = getApplicationId('/app', {} as any);
|
||||
const applicationId = await getApplicationIdAsync('/app', {} as any);
|
||||
expect(applicationId).toBe('com.expo.notdominik');
|
||||
});
|
||||
|
||||
it('throws an error if build.gradle does not exist', () => {
|
||||
it('throws an error if build.gradle does not exist', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'android/gradle.properties': 'fake file',
|
||||
'android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
expect(() => {
|
||||
getApplicationId('/app', {} as any);
|
||||
}).toThrowError(/Could not read application id/);
|
||||
await expect(getApplicationIdAsync('/app', {} as any)).rejects.toThrowError(
|
||||
/Could not read application id/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if the project does not have applicationId defined in build.gradle', () => {
|
||||
it('throws an error if the project does not have applicationId defined in build.gradle', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'android/app/build.gradle': `fake build.gradle`,
|
||||
'android/app/src/main/AndroidManifest.xml': 'fake',
|
||||
},
|
||||
'/app'
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
getApplicationId('/app', {} as any);
|
||||
}).toThrowError(/Could not read application id/);
|
||||
await expect(getApplicationIdAsync('/app', {} as any)).rejects.toThrowError(
|
||||
/Could not read application id/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('managed projects', () => {
|
||||
it('reads applicationId (Android package) from app config', () => {
|
||||
const applicationId = getApplicationId('/app', {
|
||||
it('reads applicationId (Android package) from app config', async () => {
|
||||
const applicationId = await getApplicationIdAsync('/app', {
|
||||
android: { package: 'com.expo.notdominik' },
|
||||
} as any);
|
||||
expect(applicationId).toBe('com.expo.notdominik');
|
||||
});
|
||||
|
||||
it('throws an error if Android package is not defined in app config', () => {
|
||||
expect(() => {
|
||||
getApplicationId('/app', {} as any);
|
||||
}).toThrowError(/Specify "android.package"/);
|
||||
it('throws an error if Android package is not defined in app config', async () => {
|
||||
await expect(getApplicationIdAsync('/app', {} as any)).rejects.toThrowError(
|
||||
/Specify "android.package"/
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if Android package in app config is invalid', () => {
|
||||
expect(() => {
|
||||
getApplicationId('/app', { android: { package: '1com.expo.notdominik' } } as any);
|
||||
}).toThrowError(/Specify "android.package"/);
|
||||
it('throws an error if Android package in app config is invalid', async () => {
|
||||
await expect(
|
||||
getApplicationIdAsync('/app', { android: { package: '1com.expo.notdominik' } } as any)
|
||||
).rejects.toThrowError(/Specify "android.package"/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
|
||||
import { AndroidConfig } from '@expo/config-plugins';
|
||||
import { AndroidConfig, IOSConfig } from '@expo/config-plugins';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import once from 'lodash/once';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import Log from '../../log';
|
||||
import { getProjectConfigDescription } from '../../project/projectUtils';
|
||||
import Log, { learnMore } from '../../log';
|
||||
import { getProjectConfigDescription, getUsername } from '../../project/projectUtils';
|
||||
import { promptAsync } from '../../prompts';
|
||||
import { resolveWorkflow } from '../workflow';
|
||||
import { ensureLoggedInAsync } from '../../user/actions';
|
||||
import { resolveWorkflowAsync } from '../workflow';
|
||||
|
||||
const INVALID_APPLICATION_ID_MESSAGE = `Invalid format of Android applicationId. Only alphanumeric characters, '.' and '_' are allowed, and each '.' must be followed by a letter.`;
|
||||
|
||||
@@ -17,18 +19,18 @@ export async function ensureApplicationIdIsDefinedForManagedProjectAsync(
|
||||
projectDir: string,
|
||||
exp: ExpoConfig
|
||||
): Promise<string> {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
|
||||
|
||||
try {
|
||||
return getApplicationId(projectDir, exp);
|
||||
return await getApplicationIdAsync(projectDir, exp);
|
||||
} catch (err) {
|
||||
return await configureApplicationIdAsync(projectDir, exp);
|
||||
}
|
||||
}
|
||||
|
||||
export function getApplicationId(projectDir: string, exp: ExpoConfig): string {
|
||||
const workflow = resolveWorkflow(projectDir, Platform.ANDROID);
|
||||
export async function getApplicationIdAsync(projectDir: string, exp: ExpoConfig): Promise<string> {
|
||||
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID);
|
||||
if (workflow === Workflow.GENERIC) {
|
||||
warnIfAndroidPackageDefinedInAppConfigForGenericProject(projectDir, exp);
|
||||
|
||||
@@ -71,10 +73,19 @@ async function configureApplicationIdAsync(projectDir: string, exp: ExpoConfig):
|
||||
|
||||
assert(paths.staticConfigPath, 'app.json must exist');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
`${chalk.bold(`📝 Android application id`)} ${chalk.dim(
|
||||
learnMore('https://expo.fyi/android-package')
|
||||
)}`
|
||||
);
|
||||
|
||||
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(exp);
|
||||
const { packageName } = await promptAsync({
|
||||
name: 'packageName',
|
||||
type: 'text',
|
||||
message: `What would you like your Android application id to be?`,
|
||||
initial: suggestedAndroidApplicationId,
|
||||
validate: value => (isApplicationIdValid(value) ? true : INVALID_APPLICATION_ID_MESSAGE),
|
||||
});
|
||||
|
||||
@@ -111,3 +122,19 @@ function _warnIfAndroidPackageDefinedInAppConfigForGenericProject(
|
||||
export const warnIfAndroidPackageDefinedInAppConfigForGenericProject = once(
|
||||
_warnIfAndroidPackageDefinedInAppConfigForGenericProject
|
||||
);
|
||||
|
||||
async function getSuggestedApplicationIdAsync(exp: ExpoConfig): Promise<string | undefined> {
|
||||
// Attempt to use the ios bundle id first since it's convenient to have them aligned.
|
||||
const maybeBundleId = IOSConfig.BundleIdentifier.getBundleIdentifier(exp);
|
||||
if (maybeBundleId && isApplicationIdValid(maybeBundleId)) {
|
||||
return maybeBundleId;
|
||||
} else {
|
||||
const username = getUsername(exp, await ensureLoggedInAsync());
|
||||
// It's common to use dashes in your node project name, strip them from the suggested package name.
|
||||
const possibleId = `com.${username}.${exp.slug}`.split('-').join('');
|
||||
if (isApplicationIdValid(possibleId)) {
|
||||
return possibleId;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user