Compare commits

...
17 Commits
Author SHA1 Message Date
Quinlan Jung 09bf912b9e v0.19.1 2021-07-02 11:02:09 -07:00
Quinlan Jung e701bacdaa [eas-cli] better debugging experience (#492)
* [eas-cli] better debugging

* fyi

* changelog
2021-07-02 10:59:16 -07:00
Quinlan JungandDominik Sokal cc22542031 [eas-cli] prompt to create project if not exists, more credentials guardrails (#491)
* [eas-cli] prompt to create project if not exists

* [eas-cli] more credentials manager guardrails

* Update packages/eas-cli/src/credentials/manager/ManageAndroid.ts

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

* Update packages/eas-cli/src/credentials/manager/ManageIos.ts

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

* Update packages/eas-cli/src/project/projectUtils.ts

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

* pr feedback

Co-authored-by: Dominik Sokal <[email protected]>
2021-07-02 10:53:26 -07:00
Dominik Sokal 6c53262b5b [ENG-1444] improve workflow detection (#490)
* improve workflow detection

* update CHANGELOG

* address PR feedback
2021-07-02 10:24:05 +02:00
Wojciech Kozyra 7d7d4995d8 update CHANGELOG 2021-07-01 12:23:13 +02:00
Wojciech Kozyra 977546c8c9 v0.19.0 2021-07-01 12:05:57 +02:00
Brent Vatne 53dcd4cb43 [eas-cli] Support configuring one platform at a time when running eas build (#483) 2021-06-30 17:52:37 -07:00
Wojciech Kozyra 9e18e738eb [eas-cli] use new build mutation (#485) 2021-06-29 11:54:11 +02:00
Dominik Sokal 252c7187d5 [ENG-1377] auto-suggest application id and bundle identifier (#487)
* Auto-suggest application id and bundle identifier

* update CHANGELOG

* address PR feedback
2021-06-29 10:51:48 +02:00
Quinlan Jung 90852b9557 update changelog 2021-06-28 16:01:48 -07:00
Quinlan Jung 0df16236a2 v0.18.3 2021-06-28 14:54:56 -07:00
Quinlan Jung ecaf5a4318 changelog stuff 2021-06-28 14:44:00 -07:00
Quinlan Jung 86e5e1a7b1 [eas-cli] integrate push key setup in ios builds (#473)
* [eas-cli] integrate push key setup in build flow

* nits

* fix lint

* pr feedback

* pr feedback
2021-06-28 14:43:15 -07:00
Quinlan Jung ab10094071 [eas-cli] remove nullthrows on keystore keyPassword (#484)
* [eas-cli] remove nullthrows on keystore keyPassword

* changelog
2021-06-28 10:34:45 -07:00
Dominik Sokal 2d536d73fe mock fs-extra so tests don't produce files (#480) 2021-06-28 09:43:43 +02:00
Evan Bacon 80ea8ee50b Support bundle identifiers that start with a dot (#459) 2021-06-25 15:41:48 -07:00
Brent Vatne b49c5f47d1 Update CHANGELOG 2021-06-25 15:16:06 -07:00
56 changed files with 1367 additions and 520 deletions

No files matched your search

+33 -2
View File
@@ -8,14 +8,45 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 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))
### 🧹 Chores
## [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
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.18.2"
"version": "0.19.1"
}
+24 -23
View File
@@ -69,7 +69,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.19.1/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -83,7 +83,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.19.1/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -97,7 +97,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.19.1/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -108,7 +108,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.19.1/packages/eas-cli/src/commands/analytics.ts)_
## `eas build`
@@ -128,7 +128,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.19.1/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -139,7 +139,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.19.1/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -153,7 +153,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.19.1/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:list`
@@ -169,7 +169,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.19.1/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:view [BUILD_ID]`
@@ -180,7 +180,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.19.1/packages/eas-cli/src/commands/build/view.ts)_
## `eas credentials`
@@ -191,7 +191,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.19.1/packages/eas-cli/src/commands/credentials.ts)_
## `eas device:create`
@@ -202,7 +202,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.19.1/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:list`
@@ -216,7 +216,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.19.1/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -227,7 +227,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.19.1/packages/eas-cli/src/commands/device/view.ts)_
## `eas help [COMMAND]`
@@ -260,7 +260,7 @@ OPTIONS
--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.19.1/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -277,7 +277,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.19.1/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -288,7 +288,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.19.1/packages/eas-cli/src/commands/secret/list.ts)_
## `eas submit --platform=(android|ios)`
@@ -314,7 +314,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 +368,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.19.1/packages/eas-cli/src/commands/submit.ts)_
## `eas webhook:create`
@@ -386,7 +387,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.19.1/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -400,7 +401,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.19.1/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -414,7 +415,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.19.1/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -434,7 +435,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.19.1/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -448,5 +449,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.19.1/packages/eas-cli/src/commands/webhook/view.ts)_
<!-- commandsstop -->
+7
View File
@@ -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;
+2
View File
@@ -1,3 +1,5 @@
module.exports = async () => {
process.env.TZ = 'UTC';
process.env.EAS_NO_VCS = '1';
process.env.EAS_PROJECT_ROOT = '/app';
};
+470 -2
View File
@@ -15455,8 +15455,8 @@
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
"isDeprecated": true,
"deprecationReason": "Use createAndroidBuild instead"
},
{
"name": "createAndroidManagedBuild",
@@ -15510,6 +15510,61 @@
"ofType": null
}
},
"isDeprecated": true,
"deprecationReason": "Use createAndroidBuild instead"
},
{
"name": "createAndroidBuild",
"description": "Create an Android build",
"args": [
{
"name": "appId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "job",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AndroidJobInput",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "metadata",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildMetadataInput",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CreateBuildResult",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
@@ -15622,6 +15677,61 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createIosBuild",
"description": "Create an iOS build",
"args": [
{
"name": "appId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "job",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "IosJobInput",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "metadata",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildMetadataInput",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CreateBuildResult",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@@ -16532,6 +16642,178 @@
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AndroidJobInput",
"description": "",
"fields": null,
"inputFields": [
{
"name": "type",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "BuildWorkflow",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "projectArchive",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "ProjectArchiveSourceInput",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "projectRootDirectory",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "releaseChannel",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "updates",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildUpdatesInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "secrets",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "AndroidJobSecretsInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "builderEnvironment",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "AndroidBuilderEnvironmentInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "cache",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildCacheInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "gradleCommand",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "artifactPath",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "buildType",
"description": "",
"type": {
"kind": "ENUM",
"name": "AndroidBuildType",
"ofType": null
},
"defaultValue": null
},
{
"name": "username",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "AndroidBuildType",
"description": "",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "APK",
"description": "",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "APP_BUNDLE",
"description": "",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DEVELOPMENT_CLIENT",
"description": "",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IosGenericJobInput",
@@ -17058,6 +17340,192 @@
],
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IosJobInput",
"description": "",
"fields": null,
"inputFields": [
{
"name": "type",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "BuildWorkflow",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "projectArchive",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "ProjectArchiveSourceInput",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "projectRootDirectory",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "releaseChannel",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "updates",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildUpdatesInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "distribution",
"description": "",
"type": {
"kind": "ENUM",
"name": "DistributionType",
"ofType": null
},
"defaultValue": null
},
{
"name": "secrets",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "IosJobSecretsInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "builderEnvironment",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "IosBuilderEnvironmentInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "cache",
"description": "",
"type": {
"kind": "INPUT_OBJECT",
"name": "BuildCacheInput",
"ofType": null
},
"defaultValue": null
},
{
"name": "scheme",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "buildConfiguration",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "artifactPath",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "buildType",
"description": "",
"type": {
"kind": "ENUM",
"name": "IosBuildType",
"ofType": null
},
"defaultValue": null
},
{
"name": "username",
"description": "",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "IosBuildType",
"description": "",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "RELEASE",
"description": "",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DEVELOPMENT_CLIENT",
"description": "",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IosAppBuildCredentialsMutation",
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.18.2",
"version": "0.19.1",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -13,8 +13,8 @@
"@expo/apple-utils": "0.0.0-alpha.20",
"@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/eas-build-job": "0.2.41",
"@expo/eas-json": "^0.19.0",
"@expo/json-file": "8.2.25",
"@expo/pkcs12": "0.0.4",
"@expo/plist": "0.0.11",
@@ -3,14 +3,19 @@ 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');
const originalConsoleWarn = console.warn;
beforeAll(() => {
console.warn = jest.fn();
});
afterAll(() => {
// do not remove the following line
// this fixes a weird error with tempy in @expo/image-utils
fs.removeSync(os.tmpdir());
console.warn = originalConsoleWarn;
});
beforeEach(() => {
@@ -20,35 +25,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 +77,7 @@ function initGenericProject(): ExpoConfig {
versionName "1.0"
}
}`,
'./android/app/src/main/AndroidManifest.xml': 'fake',
},
'/repo'
);
+9 -18
View File
@@ -13,7 +13,7 @@ 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';
@@ -23,7 +23,7 @@ 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(
@@ -87,21 +87,12 @@ 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,
});
},
});
}
@@ -119,7 +110,7 @@ async function ensureAndroidCredentialsAsync(
if (!shouldProvideCredentials(ctx)) {
return;
}
const androidApplicationIdentifier = getApplicationId(
const androidApplicationIdentifier = await getApplicationIdAsync(
ctx.commandCtx.projectDir,
ctx.commandCtx.exp
);
+11 -26
View File
@@ -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;
}
}
@@ -84,7 +84,7 @@ async function prepareGenericJobAsync(
ctx: BuildContext<Platform.ANDROID>,
jobData: JobData,
buildProfile: AndroidGenericBuildProfile
): Promise<Partial<Android.GenericJob>> {
): Promise<Partial<Android.Job>> {
const projectRootDirectory =
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
return {
@@ -102,7 +102,7 @@ async function prepareManagedJobAsync(
ctx: BuildContext<Platform.ANDROID>,
jobData: JobData,
buildProfile: AndroidManagedBuildProfile
): Promise<Partial<Android.ManagedJob>> {
): Promise<Partial<Android.Job>> {
const projectRootDirectory =
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
+11 -5
View File
@@ -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 (.*)/);
@@ -20,8 +23,11 @@ export function readVersionCode(projectDir: string, exp: ExpoConfig): number | u
}
}
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 ["'](.*)["']/);
+1 -1
View File
@@ -121,7 +121,7 @@ export async function ensureEasJsonExistsAsync(ctx: ConfigureContext): Promise<v
const easJson = {
builds: {
...existingEasJson,
...existingEasJson?.builds,
...(shouldInitAndroid
? {
android: ctx.hasAndroidNativeProject ? ANDROID_GENERIC_DEFAULTS : MANAGED_DEFAULTS,
+1 -1
View File
@@ -55,7 +55,7 @@ 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 {
+7 -16
View File
@@ -15,7 +15,7 @@ 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(
@@ -87,21 +87,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,
});
},
});
}
+10 -21
View File
@@ -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;
}
}
+2 -2
View File
@@ -104,7 +104,7 @@ async function prepareGenericJobAsync(
ctx: BuildContext<Platform.IOS>,
jobData: JobData,
buildProfile: IosGenericBuildProfile
): Promise<Partial<Ios.GenericJob>> {
): Promise<Partial<Ios.Job>> {
const projectRootDirectory =
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
return {
@@ -126,7 +126,7 @@ async function prepareManagedJobAsync(
ctx: BuildContext<Platform.IOS>,
jobData: JobData,
buildProfile: IosManagedBuildProfile
): Promise<Partial<Ios.ManagedJob>> {
): Promise<Partial<Ios.Job>> {
const projectRootDirectory =
path.relative(await vcs.getRootPathAsync(), ctx.commandCtx.projectDir) || '.';
const username = getUsername(ctx.commandCtx.exp, await ensureLoggedInAsync());
+3 -3
View File
@@ -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;
+11 -12
View File
@@ -1,8 +1,8 @@
import { Metadata } from '@expo/eas-build-job';
import { CredentialsSource } 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,9 +37,6 @@ 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),
@@ -50,7 +47,7 @@ export async function collectMetadata<T extends Platform>(
...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()),
@@ -63,7 +60,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 +70,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);
}
}
+48 -7
View File
@@ -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;
}
@@ -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
@@ -10,7 +10,7 @@ import {
import { createCtxMock } from '../../../__tests__/fixtures-context';
import {
canCopyLegacyCredentialsAsync,
getAppLookupParamsFromContext,
getAppLookupParamsFromContextAsync,
promptUserAndCopyLegacyCredentialsAsync,
} from '../BuildCredentialsUtils';
@@ -41,7 +41,7 @@ describe('BuildCredentialsUtils', () => {
),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
expect(canCopyLegacyCredentials).toBe(true);
});
@@ -58,7 +58,7 @@ describe('BuildCredentialsUtils', () => {
),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
expect(canCopyLegacyCredentials).toBe(false);
});
@@ -71,7 +71,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 +86,7 @@ describe('BuildCredentialsUtils', () => {
),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const canCopyLegacyCredentials = await canCopyLegacyCredentialsAsync(ctx, appLookupParams);
expect(canCopyLegacyCredentials).toBe(true);
});
@@ -109,7 +109,7 @@ describe('BuildCredentialsUtils', () => {
createFcmAsync: jest.fn(() => testLegacyAndroidFcmFragment),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
await promptUserAndCopyLegacyCredentialsAsync(ctx, appLookupParams);
expect(
@@ -131,7 +131,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
@@ -2,9 +2,11 @@ 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';
jest.mock('fs-extra');
const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
beforeAll(() => {
@@ -21,7 +23,7 @@ describe(DownloadKeystore, () => {
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 +36,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,7 +4,7 @@ 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');
@@ -31,14 +31,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,9 +1,10 @@
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);
@@ -22,14 +23,14 @@ describe(RemoveKeystore, () => {
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)
@@ -6,7 +6,7 @@ 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');
@@ -34,7 +34,7 @@ describe('SetupBuildCredentials', () => {
),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentials({ app: appLookupParams });
await setupBuildCredentialsAction.runAsync(ctx);
@@ -50,7 +50,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 +65,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
@@ -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');
@@ -67,7 +67,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
}),
'keystore.jks': 'some-binary-content',
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
appLookupParams
);
@@ -117,7 +117,7 @@ describe(SetupBuildCredentialsFromCredentialsJson, () => {
}),
'keystore.jks': 'some-binary-content',
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const appLookupParams = await getAppLookupParamsFromContextAsync(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
appLookupParams
);
@@ -140,7 +140,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,
@@ -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) {
@@ -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);
@@ -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({
+77 -58
View File
@@ -2352,14 +2352,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 +2397,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 +2417,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'];
@@ -2523,6 +2547,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 +2633,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 */
@@ -4497,18 +4564,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 +4588,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' }
@@ -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);
},
};
@@ -1,17 +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');
jest.mock('../ensureProjectExists');
const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
@@ -168,3 +175,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);
});
});
@@ -0,0 +1,67 @@
import { Platform, Workflow } from '@expo/eas-build-job';
import { vol } from 'memfs';
import { resolveWorkflowAsync } from '../workflow';
jest.mock('fs');
const originalConsoleWarn = console.warn;
beforeAll(() => {
console.warn = jest.fn();
});
afterAll(() => {
console.warn = originalConsoleWarn;
});
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,18 +3,22 @@ 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');
jest.mock('../../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() => mockJester) }));
const originalConsoleWarn = console.warn;
const originalConsoleLog = console.log;
beforeAll(() => {
console.warn = jest.fn();
console.log = jest.fn();
});
beforeEach(() => {
@@ -27,70 +31,73 @@ beforeEach(() => {
});
afterAll(() => {
fs.removeSync(os.tmpdir());
console.warn = originalConsoleWarn;
console.log = originalConsoleLog;
});
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;
}
@@ -29,15 +29,10 @@ export async function ensureProjectExistsAsync(projectInfo: ProjectInfo): Promis
const spinner = ora(`Linking to project ${chalk.bold(projectFullName)}`).start();
try {
const id = await findProjectIdByAccountNameAndSlugAsync(accountName, projectName);
const maybeId = await findProjectIdByAccountNameAndSlugNullableAsync(accountName, projectName);
if (maybeId) {
spinner.succeed(`Linked to project ${chalk.bold(projectFullName)}`);
return id;
} catch (err) {
if (err.graphQLErrors?.some((it: any) => it.extensions?.errorCode !== 'EXPERIENCE_NOT_FOUND')) {
spinner.fail(`Something went wrong while looking for ${chalk.bold(projectFullName)} on Expo`);
throw err;
}
return maybeId;
}
try {
@@ -55,6 +50,26 @@ export async function ensureProjectExistsAsync(projectInfo: ProjectInfo): Promis
}
}
/**
* Finds project by `@accountName/slug` and returns its ID, return null if the project does not exist
* @param accountName account name
* @param slug project slug
* @returns A promise resolving to Project ID, null if it doesn't exist
*/
export async function findProjectIdByAccountNameAndSlugNullableAsync(
accountName: string,
slug: string
): Promise<string | null> {
try {
return await findProjectIdByAccountNameAndSlugAsync(accountName, slug);
} catch (err) {
if (err.graphQLErrors?.some((it: any) => it.extensions?.errorCode !== 'EXPERIENCE_NOT_FOUND')) {
throw err;
}
return null;
}
}
/**
* Finds project by `@accountName/slug` and returns its ID
* @param accountName account name
@@ -73,7 +88,7 @@ async function findProjectIdByAccountNameAndSlugAsync(
* Registers new project on EAS servers
* @returns Created project's ID
*/
async function registerNewProjectAsync({
export async function registerNewProjectAsync({
accountId,
projectName,
privacy,
@@ -4,19 +4,23 @@ import os from 'os';
import path from 'path';
import { asMock } from '../../../__tests__/utils';
import { jester as mockJester } from '../../../credentials/__tests__/fixtures-constants';
import { promptAsync } from '../../../prompts';
import {
ensureBundleIdentifierIsDefinedForManagedProjectAsync,
getBundleIdentifier,
getBundleIdentifierAsync,
isWildcardBundleIdentifier,
} from '../bundleIdentifier';
jest.mock('fs');
jest.mock('../../../prompts');
jest.mock('../../../user/actions', () => ({ ensureLoggedInAsync: jest.fn(() => mockJester) }));
const originalConsoleWarn = console.warn;
const originalConsoleLog = console.log;
beforeAll(() => {
console.warn = jest.fn();
console.log = jest.fn();
});
beforeEach(() => {
@@ -31,13 +35,14 @@ beforeEach(() => {
afterAll(() => {
fs.removeSync(os.tmpdir());
console.warn = originalConsoleWarn;
console.log = originalConsoleLog;
});
const originalFs = jest.requireActual('fs');
describe(getBundleIdentifier, () => {
describe(getBundleIdentifierAsync, () => {
describe('generic projects', () => {
it('reads bundle identifier from project.', () => {
it('reads bundle identifier from project.', async () => {
vol.fromJSON(
{
'ios/myproject.xcodeproj/project.pbxproj': originalFs.readFileSync(
@@ -48,11 +53,11 @@ describe(getBundleIdentifier, () => {
'/app'
);
const bundleIdentifier = getBundleIdentifier('/app', {} as any);
const bundleIdentifier = await getBundleIdentifierAsync('/app', {} as any);
expect(bundleIdentifier).toBe('org.name.testproject');
});
it('throws an error if the pbxproj is not configured with bundle id', () => {
it('throws an error if the pbxproj is not configured with bundle id', async () => {
vol.fromJSON(
{
'ios/myproject.xcodeproj/project.pbxproj': originalFs.readFileSync(
@@ -63,30 +68,30 @@ describe(getBundleIdentifier, () => {
'/app'
);
expect(() => {
getBundleIdentifier('/app', {} as any);
}).toThrowError(/Could not read bundle identifier/);
await expect(getBundleIdentifierAsync('/app', {} as any)).rejects.toThrowError(
/Could not read bundle identifier/
);
});
});
describe('managed projects', () => {
it('reads bundleIdentifier from app config', () => {
const applicationId = getBundleIdentifier('/app', {
it('reads bundleIdentifier from app config', async () => {
const applicationId = await getBundleIdentifierAsync('/app', {
ios: { bundleIdentifier: 'com.expo.notdominik' },
} as any);
expect(applicationId).toBe('com.expo.notdominik');
});
it('throws an error if bundleIdentifier is not defined in app config', () => {
expect(() => {
getBundleIdentifier('/app', {} as any);
}).toThrowError(/Specify "ios.bundleIdentifier"/);
it('throws an error if bundleIdentifier is not defined in app config', async () => {
await expect(getBundleIdentifierAsync('/app', {} as any)).rejects.toThrowError(
/Specify "ios.bundleIdentifier"/
);
});
it('throws an error if bundleIdentifier in app config is invalid', () => {
expect(() => {
getBundleIdentifier('/app', { ios: { bundleIdentifier: '' } } as any);
}).toThrowError(/Specify "ios.bundleIdentifier"/);
it('throws an error if bundleIdentifier in app config is invalid', async () => {
await expect(
getBundleIdentifierAsync('/app', { ios: { bundleIdentifier: '' } } as any)
).rejects.toThrowError(/Specify "ios.bundleIdentifier"/);
});
});
});
@@ -1,14 +1,16 @@
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
import { IOSConfig } 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 Log from '../../log';
import Log, { learnMore } from '../../log';
import { promptAsync } from '../../prompts';
import { getProjectConfigDescription, sanitizedProjectName } from '../projectUtils';
import { resolveWorkflow } from '../workflow';
import { ensureLoggedInAsync } from '../../user/actions';
import { getProjectConfigDescription, getUsername, sanitizedProjectName } from '../projectUtils';
import { resolveWorkflowAsync } from '../workflow';
const INVALID_BUNDLE_IDENTIFIER_MESSAGE = `Invalid format of iOS bundle identifier. Only alphanumeric characters, '.' and '-' are allowed, and each '.' must be followed by a letter.`;
@@ -16,22 +18,22 @@ export async function ensureBundleIdentifierIsDefinedForManagedProjectAsync(
projectDir: string,
exp: ExpoConfig
): Promise<string> {
const workflow = resolveWorkflow(projectDir, Platform.IOS);
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
assert(workflow === Workflow.MANAGED, 'This function should be called only for managed projects');
try {
return getBundleIdentifier(projectDir, exp);
return await getBundleIdentifierAsync(projectDir, exp);
} catch (err) {
return await configureBundleIdentifierAsync(projectDir, exp);
}
}
export function getBundleIdentifier(
export async function getBundleIdentifierAsync(
projectDir: string,
exp: ExpoConfig,
{ targetName, buildConfiguration }: { targetName?: string; buildConfiguration?: string } = {}
): string {
const workflow = resolveWorkflow(projectDir, Platform.IOS);
): Promise<string> {
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
warnIfBundleIdentifierDefinedInAppConfigForGenericProject(projectDir, exp);
@@ -90,10 +92,19 @@ async function configureBundleIdentifierAsync(
assert(paths.staticConfigPath, 'app.json must exist');
Log.addNewLineIfNone();
Log.log(
`${chalk.bold(`📝 iOS Bundle Identifier`)} ${chalk.dim(
learnMore('https://expo.fyi/bundle-identifier')
)}`
);
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(exp);
const { bundleIdentifier } = await promptAsync({
name: 'bundleIdentifier',
type: 'text',
message: `What would you like your iOS bundle identifier to be?`,
initial: suggestedBundleIdentifier,
validate: value => (isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE),
});
@@ -110,7 +121,7 @@ async function configureBundleIdentifierAsync(
}
function isBundleIdentifierValid(bundleIdentifier: string): boolean {
return /^[a-zA-Z]+(\.[a-zA-Z0-9-]+)*$/.test(bundleIdentifier);
return /^[a-zA-Z0-9-.]+$/.test(bundleIdentifier);
}
function _warnIfBundleIdentifierDefinedInAppConfigForGenericProject(
@@ -135,3 +146,19 @@ export function isWildcardBundleIdentifier(bundleIdentifier: string): boolean {
const wildcardRegex = /^[A-Za-z0-9.-]+\*$/;
return wildcardRegex.test(bundleIdentifier);
}
async function getSuggestedBundleIdentifierAsync(exp: ExpoConfig): Promise<string | undefined> {
// Attempt to use the android package name first since it's convenient to have them aligned.
const maybeAndroidPackage = AndroidConfig.Package.getPackage(exp);
if (maybeAndroidPackage && isBundleIdentifierValid(maybeAndroidPackage)) {
return maybeAndroidPackage;
} 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 (isBundleIdentifierValid(possibleId)) {
return possibleId;
}
}
return undefined;
}
+5 -5
View File
@@ -3,8 +3,8 @@ import { IOSConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import { Target } from '../../credentials/ios/types';
import { resolveWorkflow } from '../workflow';
import { getBundleIdentifier } from './bundleIdentifier';
import { resolveWorkflowAsync } from '../workflow';
import { getBundleIdentifierAsync } from './bundleIdentifier';
import { XcodeBuildContext } from './scheme';
export async function resolveTargetsAsync(
@@ -14,7 +14,7 @@ export async function resolveTargetsAsync(
const result: Target[] = [];
const applicationTarget = await readApplicationTargetForSchemeAsync(projectDir, buildScheme);
const bundleIdentifier = getBundleIdentifier(projectDir, exp, {
const bundleIdentifier = await getBundleIdentifierAsync(projectDir, exp, {
targetName: applicationTarget.name,
buildConfiguration,
});
@@ -27,7 +27,7 @@ export async function resolveTargetsAsync(
for (const dependency of applicationTarget.dependencies) {
result.push({
targetName: dependency.name,
bundleIdentifier: getBundleIdentifier(projectDir, exp, {
bundleIdentifier: await getBundleIdentifierAsync(projectDir, exp, {
targetName: dependency.name,
buildConfiguration,
}),
@@ -43,7 +43,7 @@ async function readApplicationTargetForSchemeAsync(
projectDir: string,
scheme: string
): Promise<IOSConfig.Target.Target> {
const workflow = resolveWorkflow(projectDir, Platform.IOS);
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS);
if (workflow === Workflow.GENERIC) {
return await IOSConfig.Target.findApplicationTargetWithDependenciesAsync(projectDir, scheme);
} else {
+32 -1
View File
@@ -13,9 +13,13 @@ import pkgDir from 'pkg-dir';
import { graphqlClient, withErrorHandlingAsync } from '../graphql/client';
import { AppPrivacy, UpdateBranch } from '../graphql/generated';
import Log from '../log';
import { confirmAsync } from '../prompts';
import { Actor } from '../user/User';
import { ensureLoggedInAsync } from '../user/actions';
import { ensureProjectExistsAsync } from './ensureProjectExists';
import {
ensureProjectExistsAsync,
findProjectIdByAccountNameAndSlugNullableAsync,
} from './ensureProjectExists';
export function getProjectAccountName(exp: ExpoConfig, user: Actor): string {
switch (user.__typename) {
@@ -225,3 +229,30 @@ export function sanitizedProjectName(name: string) {
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}
// return project id of existing/newly created project, or null if user declines
export async function promptToCreateProjectIfNotExistsAsync(
exp: ExpoConfig
): Promise<string | null> {
const accountName = getProjectAccountName(exp, await ensureLoggedInAsync());
const maybeProjectId = await findProjectIdByAccountNameAndSlugNullableAsync(
accountName,
exp.slug
);
if (maybeProjectId) {
return maybeProjectId;
}
const fullName = await getProjectFullNameAsync(exp);
const shouldCreateProject = await confirmAsync({
message: `Looks like ${fullName} is new. Register it with EAS?`,
});
if (!shouldCreateProject) {
return null;
}
const privacy = toAppPrivacy(exp.privacy);
return await ensureProjectExistsAsync({
accountName,
projectName: exp.slug,
privacy,
});
}
+24 -2
View File
@@ -1,7 +1,29 @@
import { AndroidConfig, IOSConfig } from '@expo/config-plugins';
import { Platform, Workflow } from '@expo/eas-build-job';
import fs from 'fs-extra';
import path from 'path';
export function resolveWorkflow(projectDir: string, platform: Platform): Workflow {
return fs.pathExistsSync(path.join(projectDir, platform)) ? Workflow.GENERIC : Workflow.MANAGED;
import vcs from '../vcs';
export async function resolveWorkflowAsync(
projectDir: string,
platform: Platform
): Promise<Workflow> {
let platformWorkflowMarker;
try {
platformWorkflowMarker =
platform === Platform.ANDROID
? await AndroidConfig.Paths.getAndroidManifestAsync(projectDir)
: IOSConfig.Paths.getXcodeProjectPath(projectDir);
} catch {
return Workflow.MANAGED;
}
if (await fs.pathExists(platformWorkflowMarker)) {
return (await vcs.isFileIgnoredAsync(path.relative(projectDir, platformWorkflowMarker)))
? Workflow.MANAGED
: Workflow.GENERIC;
} else {
return Workflow.MANAGED;
}
}
@@ -8,7 +8,7 @@ import {
ensureBundleIdExistsWithNameAsync,
} from '../../credentials/ios/appstore/ensureAppExists';
import Log from '../../log';
import { getBundleIdentifier } from '../../project/ios/bundleIdentifier';
import { getBundleIdentifierAsync } from '../../project/ios/bundleIdentifier';
import { promptAsync } from '../../prompts';
import { IosSubmissionContext } from '../types';
import { sanitizeLanguage } from './utils/language';
@@ -41,7 +41,8 @@ export async function ensureAppStoreConnectAppExistsAsync(
// - for builds from the database, read bundled identifier from metadata
// - for builds uploaded from file system, prompt for the bundle identifier
// this is necessary to make submit work outside the project directory
const resolvedBundleId = bundleIdentifier ?? getBundleIdentifier(ctx.projectDir, exp);
const resolvedBundleId =
bundleIdentifier ?? (await getBundleIdentifierAsync(ctx.projectDir, exp));
const options = {
...ctx.commandFlags,
+9
View File
@@ -127,6 +127,15 @@ export default class GitClient extends Client {
!trackedFiles.includes(pathWithoutLeadingDot)
);
}
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
try {
await spawnAsync('git', ['check-ignore', '-q', filePath]);
return true;
} catch (e) {
return false;
}
}
}
async function isGitInstalledAsync(): Promise<boolean> {
+32 -20
View File
@@ -1,5 +1,5 @@
import fs from 'fs-extra';
import ignore from 'ignore';
import ignore, { Ignore } from 'ignore';
import path from 'path';
import { Client } from './vcs';
@@ -10,34 +10,46 @@ node_modules
export default class LocalClient extends Client {
public async getRootPathAsync(): Promise<string> {
const rootPath = process.env.EAS_PROJECT_ROOT ?? process.cwd();
if (!path.isAbsolute(rootPath)) {
return path.resolve(process.cwd(), rootPath);
}
return rootPath;
return getRootPath();
}
public async makeShallowCopyAsync(destinationPath: string): Promise<void> {
const srcPath = await this.getRootPathAsync();
const easIgnorePath = path.join(srcPath, '.easignore');
const gitIgnorePath = path.join(srcPath, '.gitignore');
let ignoreFile = DEFAULT_IGNORE;
if (await fs.pathExists(easIgnorePath)) {
ignoreFile = await fs.readFile(easIgnorePath, 'utf8');
} else if (await fs.pathExists(gitIgnorePath)) {
ignoreFile = await fs.readFile(gitIgnorePath, 'utf8');
}
const match = ignore().add(ignoreFile);
const srcPath = getRootPath();
const ignore = initIgnore();
await fs.copy(srcPath, destinationPath, {
filter: (srcFilePath: string) => {
if (srcFilePath === srcPath) {
return true;
}
return !match.ignores(path.relative(srcPath, srcFilePath));
return !ignore.ignores(path.relative(srcPath, srcFilePath));
},
});
}
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
return initIgnore().ignores(filePath);
}
}
function getRootPath(): string {
const rootPath = process.env.EAS_PROJECT_ROOT ?? process.cwd();
if (!path.isAbsolute(rootPath)) {
return path.resolve(process.cwd(), rootPath);
}
return rootPath;
}
function initIgnore(): Ignore {
const srcPath = getRootPath();
const easIgnorePath = path.join(srcPath, '.easignore');
const gitIgnorePath = path.join(srcPath, '.gitignore');
let ignoreFile = DEFAULT_IGNORE;
if (fs.pathExistsSync(easIgnorePath)) {
ignoreFile = fs.readFileSync(easIgnorePath, 'utf8');
} else if (fs.pathExistsSync(gitIgnorePath)) {
ignoreFile = fs.readFileSync(gitIgnorePath, 'utf8');
}
return ignore().add(ignoreFile);
}
+8 -1
View File
@@ -2,7 +2,7 @@ export abstract class Client {
// makeShallowCopyAsync should copy current project (result of getRootPathAsync()) to the specified
// destination, folder created this way will be uploaded "as is", so implementation should skip
// anything that is not commited to the repository. Most optimal solution is to create shallow clone
// using tooling provided by specific VCS, that respect all ignore rules
// using tooling provided by specific VCS, that respects all ignore rules
public abstract makeShallowCopyAsync(destinationPath: string): Promise<void>;
// Find root of the repository.
@@ -58,4 +58,11 @@ export abstract class Client {
public async getLastCommitMessageAsync(): Promise<string | null> {
return null;
}
// (optional) checks if the file is ignored, an implementation should ensure
// that if file exists and `isFileIgnoredAsync` returns true, then that file
// should not be included in the project tarball.
public async isFileIgnoredAsync(filePath: string): Promise<boolean> {
return false;
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with the eas.json",
"version": "0.18.2",
"version": "0.19.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@expo/eas-build-job": "0.2.37",
"@expo/eas-build-job": "0.2.41",
"@hapi/joi": "17.1.1",
"fs-extra": "9.0.1",
"tslib": "1.14.1"
+2 -2
View File
@@ -16,7 +16,7 @@ export type VersionAutoIncrement = boolean | 'version' | 'buildNumber';
export interface AndroidManagedBuildProfile extends Android.BuilderEnvironment {
workflow: Workflow.MANAGED;
credentialsSource: CredentialsSource;
buildType?: Android.ManagedBuildType;
buildType?: Android.BuildType;
releaseChannel?: string;
channel?: string;
distribution: AndroidDistributionType;
@@ -38,7 +38,7 @@ export interface AndroidGenericBuildProfile extends Android.BuilderEnvironment {
export interface IosManagedBuildProfile extends Omit<Ios.BuilderEnvironment, 'image'> {
workflow: Workflow.MANAGED;
credentialsSource: CredentialsSource;
buildType?: Ios.ManagedBuildType;
buildType?: Ios.BuildType;
releaseChannel?: string;
channel?: string;
distribution: IosDistributionType;
+4 -4
View File
@@ -1917,10 +1917,10 @@
xcode "^3.0.0"
xml-js "^1.6.11"
"@expo/[email protected].37":
version "0.2.37"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.37.tgz#fd6cb911e33774239d1ab7662c0ccc8efa22f7a2"
integrity sha512-B9gahwTTYs0pWQP2HnfYWcbsqmUIAdnr+iwounVqpcxHhERxGJby2JEw+Vxtl2+VMARM4w9RLgNduP5Vk/zeQw==
"@expo/[email protected].41":
version "0.2.41"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-0.2.41.tgz#d44ce7f5e7a1f08caa415913fed43f875fce4d52"
integrity sha512-tp3i7EZRMGvlJ3NyhpBpLdIxg6+CI6z6P3JgZVh57s2skN5zjzHcfjXPcw0gVH9fCouUnvhSSye8O+g2Zo1oaA==
dependencies:
"@hapi/joi" "^17.1.1"